JAVA基础再回首(二十四)——多线程的概述、实现方式、线程控制、生命周期、多线程程序练习、安全问题的解决
2016-09-30 15:56
1366 查看
JAVA基础再回首(二十四)——多线程的概述、实现方式、线程控制、生命周期、多线程程序练习、安全问题的解决
版权声明:转载必须注明本文转自程序员杜鹏程的博客:http://blog.csdn.net/m366917
这篇我们来学习多线程,java基础再回首也学了一段时间了,剩余的内容不多了,我们来继续学习。多线程的概述
学习多线程,我们先来了解一下进程的概念进程
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
说起线程,它又分为单线程和多线程
线程
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序
一个进程如果有多条执行路径,则称为多线程程序
多线程的实现(1)
如何实现多线程的程序呢?由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。
我们来通过查看API来学习多线程程序的实现
通过查看API,我们知道了有2中方式实现多线程程序。
方式1:继承Thread类
步骤
A:自定义类MyThread继承Thread类。
B:MyThread类里面重写run()
C:创建对象
D:启动线程
下面我们就自定义一个MyThread类继承Thread类启动线程
public class MyThread extends Thread { @Override public void run() { // 自己写代码 // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进 for (int x = 0; x < 100; x++) { System.out.println(x); } } } public class MyThreadDemo { public static void main(String[] args) { // 创建两个线程对象 MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); my1.start(); my2.start(); } }
这样我们就创建并启动了两个线程
start()方法:首先启动了线程,然后再由jvm去调用该线程的run()方法。
那么,我们在继承Thread类之后,为什么要重写run()方法呢?
因为不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
获取和设置线程名称
Thread类的基本获取和设置方法public final String getName():获取线程的名称。
public final void setName(String name):设置线程的名称
public class MyThread extends Thread { public MyThread() { } public MyThread(String name){ super(name); } @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } } } public class MyThreadDemo { public static void main(String[] args) { // 创建线程对象 //无参构造+setXxx() MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); //调用方法设置名称 my1.setName("阿杜"); my2.setName("杜鹏程"); my1.start(); my2.start(); //带参构造方法给线程起名字 // MyThread my1 = new MyThread("阿杜"); // MyThread my2 = new MyThread("杜鹏程"); // my1.start(); // my2.start(); //我们可以使用无参构造的方法,也可以使用带参构造的方法 } }
但是我们要获取main方法所在的线程对象的名称,该怎么办呢?
遇到这种情况,Thread类提供了一个很好玩的方法:
public static Thread currentThread():返回当前正在执行的线程对象
System.out.println(Thread.currentThread().getName());
这句话如果在main中执行,就会输出main。会返回当前执行的线程对象
线程控制
public static void sleep(long millis):线程休眠public final void join():线程加入
public static void yield():线程礼让
public final void setDaemon(boolean on):后台线程
public final void stop():中断线程
public void interrupt():中断线程
我们来逐个学习
public static void sleep(long millis):线程休眠
public class ThreadSleep extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x + ",日期:" + new Date()); // 睡眠1秒钟 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadSleepDemo { public static void main(String[] args) { ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ts1.setName("阿杜"); ts2.setName("杜鹏程"); ts1.start(); ts2.start(); } }
这样我们启动线程的时候就睡1秒执行一次
public final void join():线程加入,等待该线程终止
public class ThreadJoin extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } } } public class ThreadJoinDemo { public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin(); tj1.setName("中秋节"); tj2.setName("国庆节"); tj3.setName("圣诞节"); tj1.start(); try { tj1.join(); } catch (InterruptedException e) { e.printStackTrace(); } tj2.start(); tj3.start(); } }
运行程序,我们发现名字为中秋节的线程走完了之后才开始走下面的两个线程。
给那个线程用这个方法就是等待该线程终止后,再继续执行接下来的线程。
public static void yield():线程礼让
public class ThreadYield extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); Thread.yield(); } } } public class ThreadYieldDemo { public static void main(String[] args) { ThreadYield ty1 = new ThreadYield(); ThreadYield ty2 = new ThreadYield(); ty1.setName("阿杜"); ty2.setName("杜鹏程"); ty1.start(); ty2.start(); } }
这个方法暂停当前正在执行的线程对象,并执行其他线程。
让多个线程的执行更和谐,但是不能靠它保证一人一次。
public final void setDaemon(boolean on):守护线程
public class ThreadDaemon extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } } } public class ThreadDaemonDemo { public static void main(String[] args) { ThreadDaemon td1 = new ThreadDaemon(); ThreadDaemon td2 = new ThreadDaemon(); td1.setName("关羽"); td2.setName("张飞"); // 设置守护线程 td1.setDaemon(true); td2.setDaemon(true); td1.start(); td2.start(); Thread.currentThread().setName("刘备"); for (int x = 0; x < 5; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } } }
运行程序可以看到,当刘备执行完5次后,张飞和关于也会执行完,并不会执行100次。
将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
public final void stop():中断线程
public void interrupt():中断线程
这两个方法都是中断线程的意思,但是他们还是有区别的,我们来一起研究一下
public class ThreadStop extends Thread { @Override public void run() { System.out.println("开始执行:" + new Date()); // 我要休息10秒钟,亲,不要打扰我哦 try { Thread.sleep(10000); } catch (InterruptedException e) { // e.printStackTrace(); System.out.println("线程被终止了"); } System.out.println("结束执行:" + new Date()); } } public class ThreadStopDemo { public static void main(String[] args) { ThreadStop ts = new ThreadStop(); ts.start(); // 你超过三秒不醒过来,我就干死你 try { Thread.sleep(3000); // ts.stop(); ts.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
我们分别运行stop()方法和interrupt()方法。
可以看到一下运行结果
我们可以发现stop()方法执行后,该线程就停止了,不再继续执行了
但是interrupt()方法执行后,它会终止线程的状态,还会继续执行run方法里面的代码。
线程的生命周期图
多线程的实现(2)
方式2:实现Runnable接口步骤:
A:自定义类MyRunnable实现Runnable接口
B:重写run()方法
C:创建MyRunnable类的对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
public class MyRunnable implements Runnable { @Override public void run() { for (int x = 0; x < 100; x++) { // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用 System.out.println(Thread.currentThread().getName() + ":" + x); } } } public class MyRunnableDemo { public static void main(String[] args) { // 创建MyRunnable类的对象 MyRunnable my = new MyRunnable(); // 创建Thread类的对象,并把C步骤的对象作为构造参数传递 // Thread(Runnable target, String name) Thread t1 = new Thread(my, "阿杜"); Thread t2 = new Thread(my, "杜鹏程"); t1.start(); t2.start(); } }
这样我们就实现了多线程的第二种启动方式
多线程程序练习
某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。我们分别用两种实现多线程的方法来完成这个需求
1.继承Thread类来实现
public class SellTicket extends Thread { // 定义100张票 private static int tickets = 100; @Override public void run() { // 定义100张票 // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面 // int tickets = 100; // 是为了模拟一直有票 while (true) { if (tickets > 0) { System.out.println(getName() + "正在出售第" +(tickets--) + "张票"); } } } } public class SellTicketDemo { public static void main(String[] args) { // 创建三个线程对象 SellTicket st1 = new SellTicket(); SellTicket st2 = new SellTicket(); SellTicket st3 = new SellTicket(); // 给线程对象起名字 st1.setName("窗口1"); st2.setName("窗口2"); st3.setName("窗口3"); // 启动线程 st1.start(); st2.start(); st3.start(); } }
这样我们就实现了三个窗口同时在出售这100张票的多线程程序
2.实现Runnable接口的方式实现
public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; @Override public void run() { while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第"+ (tickets--) + "张票"); } } } } public class SellTicketDemo { public static void main(String[] args) { // 创建资源对象 SellTicket st = new SellTicket(); // 创建三个线程对象 Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); // 启动线程 t1.start(); t2.start(); t3.start(); } }
我们这个电影院售票程序,从表面上看不出什么问题,但是在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟,所以我们每次卖票延迟100毫秒
while (true) { if (tickets > 0) { // 为了模拟更真实的场景,我们稍作休息 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第"+ (tickets--) + "张票"); } } }
这样我们模拟真是场景,稍作休息了,可是运行程序后,还是会出现下面两个问题。
相同的票出现多次
CPU的一次操作必须是原子性的
还出现了负数的票
随机性和延迟导致的
这里就牵扯到了线程的安全问题,线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
多线程安全问题
如何解决多线程安全问题呢?把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
解决线程安全问题实现(1)
同步代码块格式:
synchronized(对象){ 需要同步的代码; }
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
我们多上面售票的代码进行改进
public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; //创建锁对象 private Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } } }
我们只要运用同步代码块的格式来解决线程的问题就可以,主要就是这里的对象,必须使用的是同一个锁对象。
所以我们可以来总结一下同步的特点
同步的特点
同步的前提多个线程
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
解决线程安全问题实现(2)
我们 还有一种方法可以解决多线程的安全问题同步方法:就是把同步的关键字加到方法上
private synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } }
我们只要调用这个方法就可以了
我们也可以让此方法为静态的方法
private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } }
我们要来总结一下,同步代码块的锁对象可以时任意对象。
但是,当把同步关键字加在方法上,它的对象是this
当此方法为精态方法时,它的对象是类的字节码文件对象,也就是 类名.class
好了,多线程我们先学到这里,下篇我们继续学习。
欢迎有兴趣的同学加我朋友的QQ群:点击直接加群555974449 请备注:java基础再回首我们一起来玩吧。
相关文章推荐
- JAVA基础初探(十四)多线程(线程与进程概述、线程的实现、状态、常用方法、优先级、生命周期)
- 23 API-多线程(多线程概述,多线程实现方案,线程控制常见方法,线程安全问题及解决)
- 黑马程序员——多线程——多线程概述,实现,控制与安全问题的解决
- JAVA与多线程开发(线程基础、继承Thread类来定义自己的线程、实现Runnable接口来解决单继承局限性、控制多线程程并发)
- Java基础-23总结多线程,线程实现Runnable接口,线程名字获取和设置,线程控制,线程安全,同步线程
- 0019 Servlet容器的实现:单实例多线程的线程安全问题【基础】
- Java基础 多线程 解决安全问题 等待唤醒机制 Lock Condition interrupt join setPriority yield
- java 多线程(线程间通信-解决安全问题)
- Java_基础—多线程程序实现的3种方式Thread和Runnable和Callable
- Java笔记3 多线程<1>线程概述、多线程的创建、多线程的安全问题、静态同步函数的锁、死锁
- 1多线程的概述2多线程(创建多个线程实例,并启动多个线程)的实现方式,main主方法是单线程的4多线程的实现方式5多线程模拟火车站售票出现问题7线程的声明周期
- Java_基础—多线程(匿名内部类实现线程的两种方式和设置名字的方法)
- 简单银行存款项目练习多线程安全问题解决方式——同步
- 黑马程序员--JAVA基础复习之多线程(二)线程安全与解决方法
- 黑马程序员——JAVA基础——线程---概述,创建、生命周期,控制,同步,线程通信
- Java 中的多线程-两种创建方式,定时器的应用,线程的安全问题可以用银行转账来说明
- 用java传统线程方式实现多线程轮询执行问题
- 一个简单的Java对象池实现——可用来解决SimpleDateFormat的线程安全问题
- java通过代码控制线程状态,解决线程不安全的问题。
- Java基础-23总结多线程,线程实现Runnable接口,线程名字获取和设置,线程控制,线程安全,同步线程