第3章 多线程特性 本章介绍线程之间的协作,synchronized对象锁、线程死锁的产生、Object对象监视器、线程等待机制、线程唤配机制、线程可见性重排序、线程生命周期状态。 8min 11min 3.1引出synchronized对象锁 前2章的课程详细介绍了Thread类、ThreadGroup类,以及它们常用的方法和它们之间的关联关系。在并发环境下线程之间的协作是必不可少的,在此前的课程中基本上没有运用线程之间的协作,线程对象.join()方法是线程协作的其中一种方式,但是它的使用场景较固定,不是很灵活。 在介绍synchronized对象锁之前,先参考一个单线程的案例,代码如下: //第3章/one/CooperationRunnable.java public class CooperationRunnable implements Runnable{ public int num; @Override public void run() { for(int x=0;x<100000;x++){ num++; } } } OneMain类主方法,代码如下: //第3章/one/OneMain.java public class OneMain { public static void main(String[] args) throws InterruptedException { CooperationRunnable cooperationRunnable = new CooperationRunnable(); Thread thread1 = new Thread(cooperationRunnable, "A"); thread1.start(); //启动线程 thread1.join(); //等待此线程对象销毁 System.out.println(cooperationRunnable.num); } } 执行结果如下: 100000 单线程的执行结果,肯定是没有问题的,修改OneMain类,代码如下: //第3章/one/OneMain.java public class OneMain { public static void main(String[] args) throws InterruptedException { CooperationRunnable cooperationRunnable = new CooperationRunnable(); Thread thread1 = new Thread(cooperationRunnable, "A"); thread1.start(); //启动线程 Thread thread2 = new Thread(cooperationRunnable, "B"); thread2.start(); //启动线程 Thread thread3 = new Thread(cooperationRunnable, "C"); thread3.start(); //启动线程 thread1.join(); //等待此线程对象销毁 thread2.join(); //等待此线程对象销毁 thread3.join(); //等待此线程对象销毁 System.out.println(cooperationRunnable.num); } } 多次执行代码后,观察结果。会发现每次的结果基本上都不相同,主要原因是num++并非一个原子操作,如图31所示。 图31并发产生的脏读 在多线程并发执行的情况下,可能出现例如A执行线程获得num值后,丢失了CPU的调度权,等到再次获得CPU调度权时,由于多线程并发执行的原因,此时的num值可能已经发生了变化,但是A执行线程使用的num值还是之前获得的旧值,在这种情况下就造成了数据的脏读,A执行线程继续把num值增加1,然后设置新的num值,这时又造成了数据的脏写或者覆盖。 可以使用synchronized对象锁来解决此问题,修改CooperationRunnable类,代码如下: //第3章/one/CooperationRunnable.java public class CooperationRunnable implements Runnable{ public int num; @Override public void run() { for(int x=0;x<100000;x++){ synchronized (this){ //同一个对象锁,同时只能有一个线程进入执行 num++; } } } } 运行OneMain类主方法,执行结果如下: 300000 synchronized对象锁,在锁同一个对象的情况下,同时只能有一个线程进入执行,其余阻塞等待拿锁,拿锁的过程是非公平的,并不是说阻塞等待时间越长拿锁概率越高,如图32所示。 图32阻塞等待拿锁 在使用synchronized对象锁时,需要注意锁的力度越小范围越好,毕竟是希望多线程并发执行提高效率,如果锁的范围过大,则会降低多线程并发执行的效果。 3.2synchronized对象锁 synchronized对象锁按对象类型可划分为两类,即标准对象、class对象。 14min 7min 8min 6min 3.2.1标准对象 使用方式,可以把synchronized关键字标记在对象的方法上,此方法内所有代码块属于锁范围,也可以使用synchronized(标准对象)直接锁定一个代码块区域,代码如下: //第3章/two/CooperationTwo.java public class CooperationTwo implements Runnable{ public int num; @Override public void run() { for(int x=0;x<100000;x++){ synchronized (this){ //同一个锁对象,同时只能有一个线程进入执行 num++; } } } public void run1(){ for(int x=0;x<100000;x++){ run2(); //同一个锁对象,同时只能有一个线程进入执行 } } //synchronized关键字标记在对象的方法上 public synchronized void run2(){ /* synchronized (this){ num++; }*/ num++; } } TwoMain类主方法,代码如下: //第3章/two/TwoMain.java public class TwoMain { public static void main(String[] args) throws InterruptedException { CooperationTwo cooperationTwo = new CooperationTwo(); Thread thread1 = new Thread(cooperationTwo, "A"); thread1.start(); //启动线程 Thread thread2 = new Thread(new Runnable() { @Override public void run() { cooperationTwo.run1(); } }); thread2.start(); //启动线程 for(int x=0;x<100000;x++){ synchronized (cooperationTwo){ //同一个锁对象,同时只能有一个线程进入执行 cooperationTwo.num++; } } thread1.join(); //等待thread1线程对象销毁 thread2.join(); //等待thread2线程对象销毁 System.out.println(cooperationTwo.num); } } 执行结果如下: 300000 注意: 还是同一个核心概念,只是使用方式不同。synchronized对象锁,在锁同一个对象的情况下,同时只能有一个线程进入执行,其余阻塞等待拿锁,拿锁的过程是非公平的,并不是说阻塞等待时间越长,拿锁概率越高。 3.2.2class对象 使用方式,可以把synchronized关键字标记在静态方法上,此方法内的所有代码块属于锁范围,也可以使用synchronized(class对象)直接锁定一个代码块区域,代码如下: //第3章/two/CooperationTwo.java public class CooperationTwo implements Runnable{ public static int num; @Override public void run() { for(int x=0;x<100000;x++){ synchronized (CooperationTwo.class){ //同一个锁对象,同时只能有一个线程进入执行 num++; } } } public void run1(){ for(int x=0;x<100000;x++){ run2(); //同一个锁对象,同时只能有一个线程进入执行 } } public synchronized static void run2(){ num++; } } TwoMain类主方法,代码如下: //第3章/two/TwoMain.java public class TwoMain { public static void main(String[] args) throws InterruptedException { CooperationTwo cooperationTwo = new CooperationTwo(); Thread thread1 = new Thread(cooperationTwo, "A"); thread1.start(); //启动线程 Thread thread2 = new Thread(new Runnable() { @Override public void run() { cooperationTwo.run1(); } },"B"); thread2.start(); //启动线程 for(int x=0;x<100000;x++){ synchronized (cooperationTwo){ //同一个锁对象,同时只能有一个线程进入执行 CooperationTwo.num++; } } thread1.join(); //等待thread1线程对象销毁 thread2.join(); //等待thread2线程对象销毁 System.out.println(CooperationTwo.num); } } 运行TwoMain类主方法,并观察执行结果,会发现结果基本上每次都不同,这是因为上面的代码有3个线程并行,使用了两把锁,A、B线程使用了CooperationTwo.class对象锁,但是主线程使用的是cooperationTwo标准对象锁。修改TwoMain类,代码如下: //第3章/two/TwoMain.java public class TwoMain { public static void main(String[] args) throws InterruptedException { CooperationTwo cooperationTwo = new CooperationTwo(); Thread thread1 = new Thread(cooperationTwo, "A"); thread1.start(); //启动线程 Thread thread2 = new Thread(new Runnable() { @Override public void run() { cooperationTwo.run1(); } },"B"); thread2.start(); //启动线程 for(int x=0;x<100000;x++){ synchronized (CooperationTwo.class){ //同一个锁对象,同时只能有一个线程进入执行 CooperationTwo.num++; } } thread1.join(); //等待thread1线程对象销毁 thread2.join(); //等待thread2线程对象销毁 System.out.println(CooperationTwo.num); } } 执行结果如下: 300000 3.2.3锁特性 synchronized对象锁具有的特性: 异常自动释放锁、可重入、不响应中断、非公平锁。 1. 异常自动释放锁 拿锁、释放锁是一个关联性很强的逻辑,在正常情况下,拿锁后进入代码块执行,出了代码块就会自动释放锁。如果出现异常情况,则锁不能自动释放是一个很严重的问题,可以想象等待拿锁的线程永远拿不到锁,相当于这一块业务无法处理而造成线程阻塞,甚至引起整个应用软件的瘫痪。好在synchronized对象锁有异常自动释放的特性,代码如下: //第3章/two/TwoMain2.java public class TwoMain2 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(6000); } catch (InterruptedException e) { throw new RuntimeException(e); } int num = 10 / 0; //模拟异常情况 System.out.println(Thread.currentThread().getName() +"-end"); } } }; Thread thread = new Thread(runnable,"A"); thread.start(); //启动线程 Thread.sleep(100); synchronized (runnable){ System.out.println(Thread.currentThread().getName()); } } } 执行结果如下: A main Exception in thread "A" java.lang.ArithmeticException: / by zero at cn.kungreat.book.three.two.TwoMain2$1.run(TwoMain2.java:15) at java.base/java.lang.Thread.run(Thread.java:833) 2. 可重入 相同的对象锁,在持有锁时可以直接进入,代码如下: //第3章/two/TwoMain3.java public class TwoMain3 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { synchronized (this){ try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()); reentry(); //可重入,同一个锁对象 } } public synchronized void reentry(){ System.out.println(Thread.currentThread().getName() +":reentry"); } }; Thread thread = new Thread(runnable,"A"); thread.start(); Thread.sleep(100); //睡眠100ms synchronized (runnable){ System.out.println(Thread.currentThread().getName()); } } } 执行结果如下: A A:reentry main 3. 不响应中断 synchronized对象锁不响应中断。如果要用到中断,则需要自己设计实现。 4. 非公平锁 由系统调度分配资源,并不是等待拿锁时间越久拿锁概率越高,所以说它是非公平的。 13min 3.3线程死锁的产生 由于synchronized对象锁的特性,以下代码会产生死锁,代码如下: //第3章/three/ThreeMain.java public class ThreeMain { public static void main(String[] args) { DeadRunnable deadRunnable = new DeadRunnable(); Thread thread = new Thread(deadRunnable,"A"); thread.start(); deadRunnable.deadLock(); } static class DeadRunnable implements Runnable{ private Object objA = new Object(); //objA对象锁 private Object objB = new Object(); //objB对象锁 @Override public void run() { synchronized (objA){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } deadLock(); } } public void deadLock(){ synchronized (objB){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } run(); } } } } 注意: 以上代码只为了演示死锁,其本身也存在相互调用的缺陷。A线程拿锁objA后再去调deadLock()方法,阻塞等待拿锁objB。main线程拿锁objB后再去调run()方法,阻塞等待拿锁objA。 可以通过官方提供的工具JConsole、jstack检测死锁。 3.3.1JConsole 在命令行直接运行JConsole,如图33所示。 选择自己要查看的类,如图34所示。 连接以后,选择线程,并单击“检测死锁”按钮,如图35所示。 查看死锁情况,如图36所示。 3.3.2jstack 通过jps查看pid信息,如图37所示。 使用jstackl pid,选择自己要查看的pid信息,如图38所示。 jstack会打印出pid下所有的堆栈信息,内容比较多,这里关注核心的点,如图39所示。 图33命令行 图34JConsole首页 图35检测死锁 图36查看死锁 图37查看pid 图38jstack查看pid 图39堆栈信息 3.4对象监视器 对象监视器就是synchronized对象锁中锁定的那个对象,官方文档将其称为对象监视器。对象监视器提供了几个核心的功能,如wait、notify、notifyAll。 14min 3.4.1wait() 使当前执行线程阻塞等待,直到它被唤醒或中断,具有释放当前锁的特性。如果没有被唤醒或中断,则一直阻塞等待,代码如下: //第3章/four/FourMain.java public class FourMain { public static void main(String[] args) throws Exception { MonitorRunnable monitorRunnable = new MonitorRunnable(); Thread thread = new Thread(monitorRunnable, "A"); thread.start(); //启动线程 Thread.sleep(100); //睡眠100ms synchronized (monitorRunnable){ System.out.println(Thread.currentThread().getName()); } System.out.println(Thread.currentThread().getName()+":end"); } static class MonitorRunnable implements Runnable{ @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(5000); //睡眠5s this.wait(); //使当前线程阻塞等待,直到它被唤醒或中断[释放当前锁] System.out.println("被唤醒了"); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+":end"); } } } 执行结果如下: A main main:end 注意: 以上代码A线程将会一直阻塞等待。 3.4.2wait(long timeoutMillis) 使当前执行线程阻塞等待,直到它被唤醒、中断或者超过最大等待时间,具有释放当前锁的特性,接收long入参,作为最大等待时间的毫秒数,代码如下: //第3章/four/FourMain.java public class FourMain { public static void main(String[] args) throws InterruptedException { MonitorRunnable monitorRunnable = new MonitorRunnable(); Thread thread = new Thread(monitorRunnable, "A"); thread.start(); Thread.sleep(100); //睡眠100ms synchronized (monitorRunnable){ System.out.println(Thread.currentThread().getName()); } System.out.println(Thread.currentThread().getName()+":end"); } static class MonitorRunnable implements Runnable{ @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(5000); //睡眠5s this.wait(5000); Thread.sleep(1000); //睡眠1s System.out.println("被唤醒了"); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+":end"); } } } 在经过一定量的实时时间后,A线程将正常结束生命周期,执行结果如下: A main main:end 被唤醒了 A:end 6min 3.4.3notify() 唤醒正在等待此对象监视器上的单个执行线程,代码如下: //第3章/four/FourMain.java public class FourMain { public static void main(String[] args) throws InterruptedException { MonitorRunnable monitorRunnable = new MonitorRunnable(); Thread thread = new Thread(monitorRunnable, "A"); thread.start(); Thread.sleep(100); //睡眠100ms synchronized (monitorRunnable){ System.out.println(Thread.currentThread().getName()); monitorRunnable.notify(); //唤醒正在等待此对象监视器上的单个线程 System.out.println("唤醒正在等待此对象监视器上的单个线程"); } } static class MonitorRunnable implements Runnable{ @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(5000); this.wait(); System.out.println("被唤醒了"); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+":end"); } } } 执行结果如下: A main 唤醒正在等待此对象监视器上的单个线程 被唤醒了 A:end 注意: 观察上方输出结果的第3行和第4行,并思考它们之间的先后顺序是怎么产生的。 当同一个对象监视器有多个执行线程wait()时,只会唤醒其中的一个执行线程,代码如下: //第3章/four/FourMain.java public class FourMain { public static void main(String[] args) throws InterruptedException { MonitorRunnable monitorRunnable = new MonitorRunnable(); Thread thread = new Thread(monitorRunnable, "A"); thread.start(); new Thread(monitorRunnable,"B").start(); Thread.sleep(2000); //睡眠2s,保证A、B线程都能进入wait() synchronized (monitorRunnable){ System.out.println(Thread.currentThread().getName()); monitorRunnable.notify(); //唤醒正在等待此对象监视器上的单个线程 System.out.println("唤醒正在等待此对象监视器上的单个线程"); } } static class MonitorRunnable implements Runnable{ @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); try { this.wait(); //使当前线程阻塞等待,直到它被唤醒或中断[释放当前锁] System.out.println(Thread.currentThread().getName() +"被唤醒了"); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+":end"); } } } 注意: 多次运行上方代码后,观察结果。 6min 3.4.4notifyAll() 唤醒正在等待此对象监视器上的所有执行线程,代码如下: //第3章/four/FourMain.java public class FourMain { public static void main(String[] args) throws InterruptedException { MonitorRunnable monitorRunnable = new MonitorRunnable(); Thread thread = new Thread(monitorRunnable, "A"); thread.start(); new Thread(monitorRunnable,"B").start(); Thread.sleep(2000); //睡眠2s,保证A、B线程都能进入wait() synchronized (monitorRunnable){ System.out.println(Thread.currentThread().getName()); monitorRunnable.notifyAll(); System.out.println("唤醒正在等待此对象监视器上的所有线程"); } } static class MonitorRunnable implements Runnable{ @Override public void run() { synchronized (this){ System.out.println(Thread.currentThread().getName()); try { this.wait(); //使当前线程阻塞等待,直到它被唤醒或中断[释放当前锁] System.out.println(Thread.currentThread().getName() +"被唤醒了"); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+":end"); } } } 执行结果如下: A B main 唤醒正在等待此对象监视器上的所有线程 A被唤醒了 B被唤醒了 B:end A:end 对象监视器的等待、唤醒机制配合使用可以很大程度地提高程序的灵活性,此处是模拟生产和消费的案例,代码如下: 12min //第3章/four/ConsumeMain.java public class ConsumeMain { public static void main(String[] args) { ConsumeRunnable consumeRunnable = new ConsumeRunnable(); new Thread(consumeRunnable,"A").start(); consumeRunnable.consume(); } static class ConsumeRunnable implements Runnable{ private Boolean consume = false; //生产、消费状态标识 private Object data=null; //存放消费的数据 @Override public void run() { for (int x=0;x<10;x++){ synchronized (this){ if(consume){ try { this.wait(); Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } } data = x; consume=true; this.notify(); //有没有线程wait都可以调用 System.out.println(Thread.currentThread().getName() +":生产了"+x); } } } public void consume(){ for(int x=0;x<10;x++){ synchronized (this){ if(!consume){ try { this.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() +":消费了"+data); data=null; consume=false; this.notify(); //有没有线程wait都可以调用 } } } } } 执行结果如下: A:生产了0 main:消费了0 A:生产了1 main:消费了1 A:生产了2 main:消费了2 A:生产了3 main:消费了3 A:生产了4 main:消费了4 A:生产了5 main:消费了5 A:生产了6 main:消费了6 A:生产了7 main:消费了7 A:生产了8 main:消费了8 A:生产了9 main:消费了9 注意: notify()唤醒线程后,此线程一样需要执行拿锁的过程。 3.5线程的可见性和重排序 可见性和重排序跟CPU、系统、Java底层的设计相关,这里主要关注怎么使用。可见性表示的是当某个线程修改了数据时,对于其他线程,是否能够立即感知数据被修改了。重排序表示的是在代码编译期间,对代码进行了顺序的改变。 8min 3.5.1可见性 在极端的情况下,某个线程修改了数据,对其他线程不可见,代码如下: //第3章/five/FiveMain.java public class FiveMain { static boolean running = true; //下次会修改的代码 public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { while (running){ //极端的一种演示情况 } System.out.println(Thread.currentThread().getName() +":end"); } },"A"); thread.start(); Thread.sleep(100); running=false; //修改数据 System.out.println(Thread.currentThread().getName()+":end"); } } 执行结果如下: main:end 注意: 由于A线程并没有感知到running数据被修改了,所以一直没有结束。 修改FiveMain类,代码如下: //第3章/five/FiveMain.java public class FiveMain { static volatile boolean running = true; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { while (running){ //极端的一种演示情况 } System.out.println(Thread.currentThread().getName() +":end"); } },"A"); thread.start(); Thread.sleep(100); running=false; Thread.sleep(100); System.out.println(Thread.currentThread().getName()+":end"); } } 执行结果如下: A: end main: end 使用volatile关键字可以解决可见性问题。 7min 2min 3.5.2重排序 表示的是在代码编译期间,对代码进行了顺序的改变。很难真实地验证,大家理解概念即可,代码如下: //第3章/five/PossibleReordering.java public class PossibleReordering { static int x, y = 0; //下次会修改的代码 static int a, b = 0; //下次会修改的代码 public static void main(String[] args) throws Exception { for (int num = 0;;) { num++; x=0;y=0;a=0;b=0; //重置数据 Thread threadA = new Thread(new Runnable() { @Override public void run() { a = 1; x = b; /* 重排序后 * x = b; * a = 1; */ } },"A"); Thread threadB = new Thread(new Runnable() { @Override public void run() { b = 1; y = a; } },"B"); threadA.start(); //启动线程 threadB.start(); //启动线程 threadA.join(); threadB.join(); if(x==0 && y==0) { System.out.println(num); return; } } } } 运行PossibleReordering类主方法,等待执行结果。分析上面的代码,可能产生的结果有3种。 (1) A线程先执行完: x==0、y==1。 (2) B线程先执行完: x==1、y==0。 图310重排序后的效果 (3) 一起执行完: x==1、y==1。 如果出现x==0、y==0的情况,即产生了异常数据,则可能就是由重排序造成的,如图310所示。 可以使用volatile关键字解决重排序问题,代码如下: public class PossibleReordering { static volatile int x, y = 0; static volatile int a, b = 0; public static void main(String[] args) throws Exception { for (int num = 0;;) { num++; x=0;y=0;a=0;b=0; //重置数据 Thread threadA = new Thread(new Runnable() { @Override public void run() { a = 1; x = b; /* 重排序后 * x = b; * a = 1; */ } },"A"); Thread threadB = new Thread(new Runnable() { @Override public void run() { b = 1; y = a; } },"B"); threadA.start(); //启动线程 threadB.start(); //启动线程 threadA.join(); threadB.join(); if(x==0 && y==0) { System.out.println(num); return; } } } } 注意: volatile关键字可以解决可见性、重排序的问题。思考volatile、synchronized之间的区别。 3min 7min 3.6线程生命周期状态 Thread.State是一个枚举类。一个线程在给定的时间点只能处于一种状态,这些状态是不反映任何操作系统线程状态的虚拟机线程状态。 3.6.1NEW 尚未启动的线程处于此状态,代码如下: //第3章/six/SixMain.java public class SixMain { public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); System.out.println(thread.getState()); //线程状态 } static class StateRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } } } 执行结果如下: NEW 3.6.2RUNNABLE 在执行中的线程处于此状态,代码如下: //第3章/six/SixMain.java public class SixMain { public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); thread.start(); Thread.sleep(100); //睡眠100ms System.out.println(thread.getState()); //线程状态 } static class StateRunnable implements Runnable{ @Override public void run() { //模拟线程一直在执行中的状态 while (true){ } } } } 执行结果如下: RUNNABLE 3.6.3BLOCKED 阻塞等待拿锁的执行线程处于此状态,代码如下: //第3章/six/SixMain.java public class SixMain { public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); thread.start(); synchronized (stateRunnable){//主线程先拿锁 System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); //睡眠1000ms System.out.println(thread.getState()); //线程状态 } } static class StateRunnable implements Runnable{ @Override public void run() { try { Thread.sleep(100); //睡眠100ms } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (this){ System.out.println(Thread.currentThread().getName()); } } } } 执行结果如下: main BLOCKED A 3.6.4WAITING 无限期等待的执行线程处于此状态,代码如下: //第3章/six/SixMain.java public class SixMain { public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); thread.start(); Thread.sleep(100); //睡眠100ms synchronized (stateRunnable){ System.out.println(thread.getState()); //线程状态 stateRunnable.notify(); //唤醒正在等待此对象监视器上的单个线程 } } static class StateRunnable implements Runnable{ @Override public void run() { synchronized (this){ try { this.wait(); //使当前线程阻塞等待,直到它被唤醒或中断,具有释放当前锁的特性 } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()); } } } } 执行结果如下: WAITING A 3.6.5TIMED_WAITING 最大等待指定时间的执行线程处于此状态,代码如下: //第3章/six/SixMain.java public class SixMain { public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); thread.start(); Thread.sleep(100); //睡眠100ms synchronized (stateRunnable){ System.out.println(thread.getState()); //线程状态 stateRunnable.notify(); //唤醒正在等待此对象监视器上的单个线程 } } static class StateRunnable implements Runnable{ @Override public void run() { synchronized (this){ try { this.wait(2000); //使当前线程阻塞等待,直到它被唤醒、中断或者直到经过一定量的实时时间 //具有释放当前锁的特性 } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()); } } } } 执行结果如下: TIMED_WAITING A 3.6.6TERMINATED 已退出的执行线程处于此状态,代码如下: //第3章/six/SixMain.java public class SixMain { public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); thread.start(); Thread.sleep(100); //睡眠100ms System.out.println(thread.getState()); //线程状态 } static class StateRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } } } 执行结果如下: A TERMINATED 小结 通过本章的学习,相信读者已经掌握了多线程协作的核心知识点。合理地利用这些知识点,已经足够在大多数情况下去处理多线程的并发问题。后续章节还会介绍其他协作方式,充分理解线程之间的协作概念,也是为学习后续课程奠定坚实的基础。 习题 1. 判断题 (1) Thread.sleep(2000)有释放当前锁的特性。() (2) 对象监视器.wait()有释放当前锁的特性。() (3) 对象监视器.notify()唤醒正在等待此对象监视器上的单个执行线程。() (4) volatile关键字可以解决线程之间的可见性、重排序问题。() (5) 线程的生命周期共有6种状态,在给定时间点只能处于其中的一种状态。() (6) synchronized对象锁可以锁任何对象。() (7) 对象监视器.wait(6000)可以在等待时间结束前唤醒。() 2. 选择题 (1) 可以检测死锁的官方工具有()。(多选) A. JConsole B. keytool C. jstack D. jar (2) 获得Thread当前执行线程对象正确的方法是()。(单选) A. this B. Thread.currentThread() C. this.getName() D. Thread.currentThread().getName() (3) Thread类中有降低CPU调度执行效果的方法有()。(多选) A. 线程对象.setPriority(int newPriority) B. Thread.yield() C. 线程对象.getPriority() D. 线程对象.interrupt() (4) volatile关键字解决的问题有()。(多选) A. 可见性 B. 并发脏读 C. 并发脏写 D. 重排序 3. 填空题 (1) 查看执行结果,并补充代码,代码如下: //第3章/answer/SixMain.java public class SixMain { private static boolean running = true; public static void main(String[] args) throws InterruptedException { StateRunnable stateRunnable = new StateRunnable(); Thread thread = new Thread(stateRunnable,"A"); thread.start(); Thread.sleep(100); //睡眠100ms System.out.println(thread.getState()); //线程状态 ; } static class StateRunnable implements Runnable{ @Override public void run() { while (running){ } System.out.println(Thread.currentThread().getName()+":end"); } } } 执行结果如下: RUNNABLE A:end (2) 根据业务要求补全代码,A、B两个执行线程轮流按照1~100的顺序打印数字,代码如下: //第3章/answer/LoopPrint.java public class LoopPrint { public static void main(String[] args) throws InterruptedException { LoopRunnable loopRunnable = new LoopRunnable(); Thread threadA = new Thread(loopRunnable, ); Thread threadB = new Thread(loopRunnable, ); threadA.start(); threadB.start(); } static class LoopRunnable implements Runnable{ private volatile int num = 1; @Override public void run() { for (;num<=100;){ synchronized (this){ System.out.println(Thread.currentThread().getName() +":"+num); num++; ; try { ; } catch (InterruptedException e) { throw new RuntimeException(e); } } } synchronized (this){ ; } } } } (3) 根据业务要求补全代码,启动3个执行线程A、B、C,每个执行线程将自己的名称轮流打印5遍,打印顺序是ABCABC…,代码如下: //第3章/answer/ThreePrint.java public class ThreePrint { public static void main(String[] args) { LoopRunnable loopRunnable = new LoopRunnable(); new Thread(loopRunnable, "A").start(); new Thread(loopRunnable, "B").start(); new Thread(loopRunnable, "C").start(); } static class LoopRunnable implements Runnable { private volatile int loopIndex = 0; private final String[] loopNames = {"A", "B", "C"}; @Override public void run() { for (int x = 0; x < 5; x++) { synchronized (this) { String name = Thread.currentThread().getName(); //名称不匹配时一直循环 while (!name.equals(loopNames[loopIndex])) { try { ; } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.print(name); //消费名称 loopIndex++; if (loopIndex == loopNames.length) { loopIndex = 0; //重置指针 } ; } } } } } (4) 根据业务要求补全代码,包括生产和消费,生产的总量为10,代码如下: //第3章/answer/ConsumeMain.java public class ConsumeMain { public static void main(String[] args) { ConsumeRunnable consumeRunnable = new ConsumeRunnable(); new Thread(consumeRunnable,"A").start(); consumeRunnable.consume(); } static class ConsumeRunnable implements Runnable{ private Boolean consume = false; //生产、消费状态标识 private Object data=null; //存放消费的数据 @Override public void run() { for (int x=0;x<10;x++){ synchronized (this){ if(consume){ try { ; Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } data = x; consume=true; ; //有没有线程wait都可以调用 System.out.println(Thread.currentThread().getName() +":生产了"+x); } } } public void consume(){ for(int x=0;x<10;x++){ synchronized (this){ if(!consume){ try { ; } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() +":消费了"+data); data=null; consume=false; ; //有没有线程wait都可以调用 } } } } }