找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索

[ JAVA开发技术 ] 【守望者 j2se】wait,notify,notifyAll实现线程调度

2014-10-11 21:06| 发布者: zhouy | 查看: 1471 | 收藏

摘要: 我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度或者说是线程的同步控制。1.wait() 方法用来控制当前线程停止执行 ...

我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度或者说是线程的同步控制。


1.wait() 方法用来控制当前线程停止执行,等待其他线程对此Object实例调用notify或者notifyAll方法之后再继续执行

2.wait(long timeout) 此方法的作用和wait()类似,但是增加了一个超时的设置,如果等待时间超过了timeout设定的毫秒数,那么当前线程会继续执行

3.notify()方法从所有wait线程中选择一个线程,让它开始执行

4.notifyAll()方法通知所有等待此对象的线程,开始执行


上面的解释字面意思上很容易理解,但是实际使用起来,却并不是那么简单,我们以一个实际的例子来看下如何使用这些方法。
假定我们有两个线程要打印1到9这9个数字,要求第一个线程打印1,2,3然后停止打印,由线程2打印4,5,6,然后线程2停止打印,通知线程1继续打印7,8,9.
需求很简单,我们可以建两个Runnable类,假定为PrinterA和PrinterB,先由PrinterB等待,由PrinterA打印1,2,3;PrinterA打印完之后通知PrinterB,然后自己进入等待状态;PrintB获得PrinterA的通知之后开始打印4、5、6,打印完毕之后需要通知PrinterA;然后PrinterA得到通知之后开始打印剩下的7、8、9。任务就完成了。

public class WaitNotifyDemo {     
   private volatile int val = 1;       
   private synchronized void printAndIncrease() {  
             System.out.println(Thread.currentThread().getName() + " prints " + val);   
             val++;    
    }   

// print 1,2,3 7,8,9       
public class PrinterA implements Runnable {             
  @Override               
public void run() {          
              while (val <= 3) {                
                printAndIncrease();                   
  }  

// print 1,2,3 then notify printerB                       
synchronized (WaitNotifyDemo.this) {                              
            System.out.println("printerA printed 1,2,3; notify PrinterB");             
            WaitNotifyDemo.this.notify();                 
  }           

try {                             
    while (val <= 6) {                                  
      synchronized (WaitNotifyDemo.this) {                         
             System.out.println("wait in printerA");                                       
            WaitNotifyDemo.this.wait();                             
           }                            
      }                             
     System.out.println("wait end printerA");                 
   } catch (InterruptedException e) {                              
      e.printStackTrace();                       
}                      
  while (val <= 9) {                     
           printAndIncrease();               
   }                       
        System.out.println("printerA exits");     
  }       
}        

// print 4,5,6 after printA print 1,2,3   
public class PrinterB implements Runnable {              
  @Override                
  public void run() {                   
     while (val < 3) {                    
           synchronized (WaitNotifyDemo.this) {          
               try {                                         
                     System.out.println("printerB wait for printerA printed 1,2,3");                                              
                     WaitNotifyDemo.this.wait();                                             
                      System.out.println("printerB waited for printerA printed 1,2,3");                                    
                  } catch (InterruptedException e) {                                          
                       e.printStackTrace();                                     
              }                           
     }                      
  }                       
  while (val <= 6) {                             
           printAndIncrease();                     
   }                        
   System.out.println("notify in printerB");                       
   synchronized (WaitNotifyDemo.this) {                              
             WaitNotifyDemo.this.notify();                   
}                       
    System.out.println("notify end printerB");                   
     System.out.println("printerB exits.");               
  }      
}      
  public static void main(String[] args) {              
       WaitNotifyDemo demo = new WaitNotifyDemo();             
        demo.doPrint();        
}      
private void doPrint() {             
      PrinterA pa = new PrinterA();          
      PrinterB pb = new PrinterB();              
     Thread a = new Thread(pa);               
      a.setName("printerA");                
     Thread b = new Thread(pb);               
     b.setName("printerB");                
     // 必须让b线程先执行,否则b线程有可能得不到锁,执行不了wait,而a线程一直持有锁,会先notify了             
      b.start();               
      a.start();      
  }}


我们先把所有代码奉上了,你可以自己调试代码,在实际执行中来了解代码的实现机制。下面我们逐步分析下我们是如何控制两个线程调度的。
首先看main方法,在main方法中我们初始化了一个WaitNotifyDemo实例,然后调用了这个实例的doPrint方法。
在doPrint方法中我们使用PrinterA和PrinterB的实例初始化了两个线程,然后启动他们。

  private void doPrint() {                
        PrinterA pa = new PrinterA();        
        PrinterB pb = new PrinterB();          
        Thread a = new Thread(pa);           
        a.setName("printerA");              
        Thread b = new Thread(pb);             
        b.setName("printerB");               
        // 必须让b线程先执行,否则b线程有可能得不到锁,执行不了wait,而a线程一直持有锁,会先notify了          
        b.start();          
        a.start();     
   }


这里需要注意必须让b线程先执行,这样b线程才能先获得WaitNotifyDemo实例上的锁,并开始等待。在PrinterB的run方法中开始等待的代码片段如下:
       while (val < 3) {                              
           synchronized (WaitNotifyDemo.this) {                                    
               try {                                                
                        System.out.println("printerB wait for printerA printed 1,2,3");                  
                         WaitNotifyDemo.this.wait();                                           
                          System.out.println("printerB waited for printerA printed 1,2,3");                           
                     } catch (InterruptedException e) {       
                             e.printStackTrace();                   
                     }                              
     }                    
}


这里有一个while循环,如果val的值小于3,那么在WaitNotifyDemo的实例的同步块中调用WaitNotifyDemo.this.wait()方法,这里要注意无论是wait,还是notify,notifyAll方法都需要在其实例对象的同步块中执行,这样当前线程才能获得同步实例的同步控制权,如果不在同步块中执行wait或者notify方法会出现java.lang.IllegalMonitorStateException异常。另外还要注意在wait方法两边的同步块会在wait执行完毕之后释放对象锁。
这样PrinterB就进入了等待状态,我们再看下PrinterA的run方法:

            while (val <= 3) {        
                   printAndIncrease();                     
             }                        
            // print 1,2,3 then notify printerB                      
             synchronized (WaitNotifyDemo.this) {        
                    System.out.println("printerA printed 1,2,3; notify printerB");        
                     WaitNotifyDemo.this.notify();            
             }                     
           try {                         
             while (val <= 6) {                  
                      synchronized (WaitNotifyDemo.this) {                                   
                      System.out.println("wait in printerA");                       
                      WaitNotifyDemo.this.wait();                            
            }                             
  }                                
     System.out.println("wait end printerA");              
   } catch (InterruptedException e) {                      
          e.printStackTrace();                       
  }
这里首先打印了1、2、3,然后在同步块中调用了WaitNotifyDemo实例的notify方法,这样PrinterB就得到了继续执行的通知,然后PrinterA进入等待状态,等待PrinterB通知。


我们再看下PrinterB run方法剩下的代码:

        while (val <= 6) {                           
          printAndIncrease();                        
         }                        
            System.out.println("notify in printerB");                       
         synchronized (WaitNotifyDemo.this) {                               
              WaitNotifyDemo.this.notify();                       
          }                        
          System.out.println("notify end printerB");                      
          System.out.println("printerB exits.");printerB首先打印了4、5、6,然后在同步块中调用了notify方法,通知PrinterA开始执行。
          PrinterA得到通知后,停止等待,打印剩下的7、8、9三个数字,如下是PrinterA run方法中剩下的代码:

          while (val <= 9) {        
                        printAndIncrease();                      
           }
           整个程序就分析完了,run下程序,控制台输出如下:
           printerB wait for printerA printed 1,2,3
           printerA prints 1
           printerA prints 2
           printerA prints 3
           PrinterA printed 1,2,3; notify PrinterB
           printerB waited for printerA printed 1,2,3
           printerB prints 4
           printerB prints 5
           printerB prints 6
           notify in printerB
          notify end printerB
          PrinterB exits.
          wait end printerA
          printerA prints 7
          printerA prints 8
          printerA prints 9
          PrinterA exits
从输出内容上也可以看到wait,notify的执行过程在用wait,notify做线程同步是要特别注意下面两点:


1.不要选择字符串,Integer,Long,Type之类的对象做同步对象,因为这些类型在jvm中都有一些特殊的处理,有可能会有意想不到的情况。比如Integer,JVM对小于128的数字做了cache,如果你用Integer做同步对象的话,可能不同的逻辑锁定了相同的同步块。这类问题调试起来也不好调试,所以最好避免这样使用。

2.在调用obj.notify(),obj.wait方法时要在synchronized(obj)块中进行调用,否则会出现java.lang.IllegalMonitorStateException异常

推荐阅读

【守望者  j2se】ConcurrentHashMap原理分析
【守望者 j2se】ConcurrentHashMap原
集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据
【守望者  j2se】双向链表模拟
【守望者 j2se】双向链表模拟
我们熟悉了java单向链表的模拟,现在我就必须开始双向链表的模拟的.1.基础结构
【守望者 高并发】现有高并发WEB服务器 lighttpd Apache Nginx比较
【守望者 高并发】现有高并发WEB服务器
lighttpd网络服务器基于的Lighttpd的网络服务器具有这样的特点:占用内存资源
【守望者 高并发】C10K/C500K与I/O框架
【守望者 高并发】C10K/C500K与I/O框架
C10K、C/500K问题C10K 的意思是10000并发请求,C500K意思是500 000并发请求,
【守望者  j2se】虚拟机各部分内存溢出情况
【守望者 j2se】虚拟机各部分内存溢出
通过简单的小例子程序,演示java虚拟机各部分内存溢出情况:(1).java堆溢出:
【守望者  JMM】理解volatile内存语义
【守望者 JMM】理解volatile内存语义
理解volatile变量对写多线程程序还是很有帮助的,这样就会避免一上来就是syn这
【守望者 高并发】使用CAS实现高效并发处理
【守望者 高并发】使用CAS实现高效并发
守望者:在并发处理应用中,一般使用锁的方式来解决竞争问题,但锁的效率比较
【守望者  j2se】吃透 java I/O 工作机制-1
【守望者 j2se】吃透 java I/O 工作机
I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这
【守望者 大数据】Mahout学习路线图
【守望者 大数据】Mahout学习路线图
Hadoop家族产品,常用的项目包括Hadoop, Hive, Pig, HBase, Sqoop, Mahout, Z
【守望者 j2se】ConcurrentMap之putIfAbsent(key,value)用法讨论
【守望者 j2se】ConcurrentMap之putIfA
先看一段代码:public class Locale { private final static MapString, Lo
【守望者  javascript】判断IE浏览器世界上最短的代码
【守望者 javascript】判断IE浏览器世
最短的IE判定var ie=!-分析以前最短的IE判定借助于IE不支持垂直制表符的特性
【守望者 大数据】机器学习已成为大数据的基石
【守望者 大数据】机器学习已成为大数
机器学习(Machine Learning, ML)是一门多领域交叉学科,涉及概率论、统计学、
【守望者  j2se】多线程与并发知识点总结
【守望者 j2se】多线程与并发知识点总
对于多线程和并发编程这个比较大的技术模块,我们会整理一些帖子方便知识点的
【守望者  j2se】二叉树模拟
【守望者 j2se】二叉树模拟
接着我们就要写一个比较复杂的数据结构的,但是这个数据结构是很重要的,假如
【守望者 SRS  】SRS 源代码分析笔记(0.9.194)-分析服务器对端口的监听 ...
【守望者 SRS 】SRS 源代码分析笔记(
第一部分 分析服务器对端口的监听 端口监听与初始化(一)全局变量_srs_confi

行业聚焦  面试交流  职位推荐  开发视频   技术交流  腾讯微博  新浪微博

友情链接:课课家教育  阿里云  鲜果  W3Cfuns前端网  中国企业家  环球企业家  投资界  传媒梦工场  MSN中文网  Android开发者社区  cnbeta  投资中国网  又拍云存储  美通说传播  IT茶馆  网商在线  商业评论网  TechOrange  IT时代周刊  3W创新传媒  开源中国社区  二维工坊  Iconfans  推酷  智能电视网  FreeBuf黑客与极客  财经网  DoNews  凤凰财经  新财富  eoe移动开发者社区  i黑马  网易科技  新浪科技  搜狐IT  创业家  创业邦  腾讯财经  福布斯中文网  天下网商  TechWeb  雷锋网  新浪创业  和讯科技  品途O2O  极客公园  艾瑞网  抽屉新热榜  卖家网  人民网通信频道  拉勾网  创新派  简单云主机  

手机版|黑名单|守望者在线 在线教育 linux 高级程序设计 C/C++ 大数据 ( 蜀ICP备14029946号

成都守望者科技有限公司 © 2013-2016 All Rights Reserved