关于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方法,在线程睡眠期间,并没有释放同步监视器,所以其他线程亦无法获得该同步监视器的锁定(因为任意时刻只能有一个线程获得某个对象的锁定),从而无法执行被锁定的对象的方法。线程获得某对象的同步监视器有如下三个方法:

  1. 执行对象的同步方法;
  2. 执行使对对象同步的同步块;
  3. 执行对象对应的类的静态同步方法;

  接下来看看,如果把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时,线程压根没有获得同步监视器,又何来解锁呢?嘻嘻,看了几遍以为自己懂了,差远呢,骚年!

  第一次写技术博客,本人乃菜鸟级别,表达先天不足,表述难免有误,看官多多指教。纸上得来终觉浅,绝知此事要躬行。

posted @ 2014-04-25 21:53  麦香小瑜儿  阅读(648)  评论(0编辑  收藏  举报