关于Thread的sleep方法及同步监视器对象的wait方法在释放同步监视器的区别
今日在看一本java基本教材时,了解到Thread的sleep()方法不会导致当前线程释放同步监视器,而同步监视器对象的wait方法会令线程解锁同步监视器。于是做了以下实验验证该功能:
1 package com.lauyu; 2 3 public class SayWord 4 { 5 public synchronized void canNotSleep() 6 { 7 //输出当前线程的名称 8 System.out.println("当前线程是:" + Thread.currentThread().getName()); 9 10 System.out.println("*** 不睡觉 ***"); 11 } 12 13 public synchronized void canSleep() 14 { 15 //输出当前线程的名称 16 System.out.println("当前线程是:" + Thread.currentThread().getName()); 17 18 System.out.println("... 要睡觉 ..."); 19 System.out.println(Thread.currentThread().getName()+" 开始睡觉"); 20 try 21 { 22 //在同步方法内调用当前线程的sleep,不能导致线程丢失监视器 23 Thread.sleep(5000); 24 //wait(5000); 25 } 26 catch(Exception e) 27 { 28 e.printStackTrace(); 29 } 30 System.out.println(Thread.currentThread().getName()+" 睡觉结束"); 31 } 32 }
以上定义了一个SayWord类,该类有两个同步方法:canNotSleep()、canSleep(),其中canSleep()方法内部会执行Thread.sleep(),令执行该方法的线程睡眠5s。下面再看主文件的code。
1 package com.lauyu; 2 3 //下面是子线程,即是需要被执行的子线程,实现了Runnable接口,如果想创建有返回值的子线程,就要实现Callable接口 4 class AwakeThread implements Runnable 5 { 6 SayWord sw; 7 public AwakeThread(SayWord sw) 8 { 9 this.sw = sw; 10 } 11 12 //实现Runnable接口的run方法 13 public void run() 14 { 15 for(int i=0;i<3;i++) 16 { 17 System.out.println(""); 18 System.out.println("开始执行线程:" + Thread.currentThread().getName()); 19 //线程执行体是执行sw的 不会 sleep的方法 20 sw.canNotSleep(); 21 } 22 } 23 } 24 25 class SleepThread implements Runnable 26 { 27 SayWord sw; 28 public SleepThread(SayWord sw) 29 { 30 this.sw = sw; 31 } 32 33 //实现Runnable接口的run方法 34 public void run() 35 { 36 for(int i=0;i<3;i++) 37 { 38 System.out.println(""); 39 System.out.println("开始执行线程:" + Thread.currentThread().getName()); 40 //线程执行体是执行sw的会sleep的方法 41 sw.canSleep(); 42 } 43 } 44 } 45 46 public class ThreadPoolTest 47 { 48 public static void main(String[] args) throws Exception 49 { 50 SayWord sw = new SayWord(); 51 //输出主线程的名称 52 System.out.println("主线程是:" + Thread.currentThread().getName()); 53 54 //新建两个子线程,target为SubThread1、SubThread2对象 55 Thread thread1 = new Thread(new AwakeThread(sw), "不会睡觉的线程"); 56 Thread thread2 = new Thread(new SleepThread(sw), "会睡觉的线程"); 57 58 //设置线程thread2有最大的优先级,线程thread1有最小的优先级,以尽量保证thread2优先被执行 59 thread2.setPriority(Thread.MAX_PRIORITY); 60 thread1.setPriority(Thread.MIN_PRIORITY); 61 62 //启动子线程 63 thread2.start(); 64 thread1.start(); 65 } 66 }
首先定义两个线程类AwakeThread、SleepThread,其中AwakeThread的run方法执行了SayWord对象的canNotSleep方法,SleepThread的run方法执行了SayWord对象的canSleep方法。接着在主类的mian方法创建了两条线程,target分别为AwakeThread、SleepThread的对象。AwakeThread、SleepThread的对象传入的是同一个SayWord对象。启动两条线程,让thread2先执行(即是会睡觉的线程)。如果thread2真的先于thread1执行,结果如下:
主线程是:main 开始执行线程:会睡觉的线程 //显然是会睡觉的线程开始执行 当前线程是:会睡觉的线程 ... 要睡觉 ... 会睡觉的线程 开始睡觉 //开始睡觉 开始执行线程:不会睡觉的线程 //睡觉后,系统调度不会睡觉的线程执行 会睡觉的线程 睡觉结束 //可以看到,不会睡觉的线程只是执行了还没有进入SayWord对象的canNotSleep方法的方法体,同步监视器的方法没有被调用。一直等到sleep结束,又开始执行会睡觉 //线程。 开始执行线程:会睡觉的线程 //接下来又开始执行下一个循环 当前线程是:会睡觉的线程 ... 要睡觉 ... 会睡觉的线程 开始睡觉 会睡觉的线程 睡觉结束 开始执行线程:会睡觉的线程 当前线程是:会睡觉的线程 ... 要睡觉 ... 会睡觉的线程 开始睡觉 会睡觉的线程 睡觉结束 //到这里会,睡觉的线程执行完毕,释放同步监视器 当前线程是:不会睡觉的线程 //刚刚不会睡觉的线程没有执行完的部分,这里开始接着执行 *** 不睡觉 *** 开始执行线程:不会睡觉的线程 当前线程是:不会睡觉的线程 *** 不睡觉 *** 开始执行线程:不会睡觉的线程 当前线程是:不会睡觉的线程 *** 不睡觉 ***
可以看出,如果线程获得了同步监视器,在释放同步监视器之前,即使执行了sleep方法,在线程睡眠期间,并没有释放同步监视器,所以其他线程亦无法获得该同步监视器的锁定(因为任意时刻只能有一个线程获得某个对象的锁定),从而无法执行被锁定的对象的方法。线程获得某对象的同步监视器有如下三个方法:
- 执行对象的同步方法;
- 执行使对对象同步的同步块;
- 执行对象对应的类的静态同步方法;
接下来看看,如果把canSleep方法内的Thread.sleep方法改为wait,看看结果如何?
try { //在同步方法内调用当前线程的sleep,不能导致线程丢失监视器 //Thread.sleep(5000); wait(5000); }
输出的结果
主线程是:main 开始执行线程:会睡觉的线程 当前线程是:会睡觉的线程 ... 要睡觉 ... 会睡觉的线程 开始睡觉 //会睡觉线程开始睡觉 开始执行线程:不会睡觉的线程 //会睡觉线程sleep后,系统调度不会睡觉的线程 当前线程是:不会睡觉的线程 //执行SayWord的canNotSleep方法 *** 不睡觉 *** 开始执行线程:不会睡觉的线程 //还是执行不会睡觉线程 当前线程是:不会睡觉的线程 *** 不睡觉 *** 开始执行线程:不会睡觉的线程 当前线程是:不会睡觉的线程 *** 不睡觉 *** //不会睡觉线程到此政治 会睡觉的线程 睡觉结束 //sleep结束后,开始继续执行会睡觉线程 开始执行线程:会睡觉的线程 当前线程是:会睡觉的线程 ... 要睡觉 ... 会睡觉的线程 开始睡觉 会睡觉的线程 睡觉结束 开始执行线程:会睡觉的线程 当前线程是:会睡觉的线程 ... 要睡觉 ... 会睡觉的线程 开始睡觉 会睡觉的线程 睡觉结束
由上可知,当线程获得同步监视器后,执行wait方法,就会令执行wait方法的线程释放同步监视器,然后如果有其他线程有需要,就能锁定该对象,执行对象的同步方法。wait时间结束,线程回到睡眠状态。
此外,在调试过程中,还发现即是认为设置了thread1、thread2的优先级,但是还是有时出现低优先级的线程被先执行。目前肤浅地认识到线程与操作系统机制有关,线程执行是抢占式的,看来不能完全依赖于通过设置线程优先级来控制线程的执行顺序。
在设计这个对比过程中,还犯了一个错,就是把Thread.sleep()方法放在了run方法时,如下:
class SleepThread implements Runnable { SayWord sw; public SleepThread(SayWord sw) { this.sw = sw; } //实现Runnable接口的run方法 public void run() { for(int i=0;i<3;i++) { System.out.println(""); System.out.println("开始执行线程:" + Thread.currentThread().getName()); //线程执行体是执行sw的会sleep的方法 sw.canSleep(); System.out.println(Thread.currentThread().getName()+" 开始睡觉"); //这一段本来应该放在canSleep方法体内 try { //在同步方法内调用当前线程的sleep,不能导致线程丢失监视器 Thread.sleep(5000); //wait(5000); } catch(Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 睡觉结束"); } } }
sleep放在了canSleep方法体外,就出现当会睡觉线程进入睡眠状态时,不会睡觉线程立刻获得执行。一开始以为是sleep方法或导致解锁,后来发现自己理解错了。线程要获得对象的同步监视器,要执行对象的同步方法(就是前面提到的三个方式某一种),如果把sleep放在canSleep方法体外面,线程已经完成了上锁--修改--解锁过程,也就是说执行sleep时,线程压根没有获得同步监视器,又何来解锁呢?嘻嘻,看了几遍以为自己懂了,差远呢,骚年!
第一次写技术博客,本人乃菜鸟级别,表达先天不足,表述难免有误,看官多多指教。纸上得来终觉浅,绝知此事要躬行。