最近在学习Java中线程同步和线程通信问题,发现在Java基础类Object中有两个线程相关方法wait()和notify(),于是就做了点小的实验,下面就是实验的内容。
在介绍实验之前我们要弄清楚synchronized关键字的真正语义。
synchronized关键字是用来声明在执行某个代码块时,执行该代码块的线程必须获得某个特定对象的对象监视器(java 官方称其为moniter,民间传说称其为锁,下面我一概简称监视器),而一个对象的监视器只有一个。这句话什么意思呢,我们来看个例子代码。
StringBuffer sb = new StringBuffer();
synchronized(sb){//代码段1}
这段代码就是说当某个线程要执行代码段1时,该线程必须先获取对象sb的监视器,同时该线程将会持有该监视器一直到代码段1执行完毕才会将其释放。我们知道,一个对象的监视器只有一个,如果该对象的监视器被线程线程A持有了,那么任何其他线程在执行形如上面的代码时,也就是说在执行需要获取监视器的代码块时,都会被阻塞,因为他们要等待线程A释放掉监视器,也就是说等待线程A把同步代码块执行结束,他们才有可能获取处理器。
在方法前加synchronized其实就相当于
public void functionName(){
synchronized(this){
//方法执行代码
}
}
下面我们来看段代码
一般我们写线程等待会写成这样:
synchronized(obj){
//调用obj的方法
Thread.sleep(10000);
//调用obj的方法
}
在上面的代码中我调用了sleep方法是线程休眠的10秒,通过上面对synchronized的解释,我们知道如果有另一个线程要获取obj的监视器以执行同步代码块是不行的,虽然休眠的线程没有在执行,但是它却持有着obj的监视器,因此其他任何需要obj监视器才能执行的代码都必须等待。
wait()的作用就是使线程阻塞并且会暂时释放掉对象的监视器,notify()方法则是通知那些暂时释放监视器的线程回收监视器(注意这里只是通知,并不是立即,具体什么时候回收后面会解释)。
代码段2
synchronized(obj){
//调用obj的方法1
obj.wait()
//调用obj的方法2
}
代码段3
synchronized(obj){
//调用obj中的某些方法
obj.notify()
}
上面两段代码的执行都需要线程获取obj的监视器,而在代码段2中的wait将会阻塞当前线程,有就是说当调用了obj的方法1之后线程不会继续掉用obj的方法2而是将线程转为阻塞状态。按照我上面对synchronized的解释,当线程没有将代码段2中的代码全部执行完,其他线程是没有资格去执行代码段3的,但是wait()却可以让代码段3得到执行,因为wait()将会暂时释放掉obj的监视器,那么就是说代码段3就能够获取obj的监视器了,换言之代码段3就可以执行了。
在代码段3中我们调用了obj中的一些方法,最后我们调用了obj.notify()方法,它是干嘛的呢?现在我们假设执行代码段2的线程叫线程B,执行代码段3的线程叫线程C。线程B是阻塞的,但它暂时释放了obj的监视器,当前线程C是持有obj的监视器的,那儿在线程C中调用了obj.notify就是在通知线程B回收暂时释放的obj的监视器,同时会将线程B转为就绪状态。那么这个时候线程B是否就回收了obj的监视器呢?答案是:没有。那什么时候回收呢?当线程B从就绪状态转为执行状态时,线程B就是立即回收obj的监视器,然后执行obj的方法2。
下面是我实际测试时用的代码,通过分析运行结果和我上面的结论相信很好理解。
package individual.hcx.test; public class Test { public static void main(String[] args) throws Exception{ StringBuffer sb = new StringBuffer(); MyThread thread = new MyThread(sb); MyThread2 thread2 = new MyThread2(sb); thread2.start(); Thread.sleep(100); thread.start(); } } class MyThread extends Thread{ StringBuffer sb; public MyThread(StringBuffer sb){ this.sb = sb; } @Override public void run(){ do{ synchronized(sb) { System.out.println("生成随机数!"); sb.delete(0, sb.length()); sb.append((int)(Math.random()*100)); sb.notify(); } }while(true); } } class MyThread2 extends Thread{ StringBuffer sb; public MyThread2(StringBuffer sb){ this.sb = sb; } @Override public void run(){ synchronized (sb) { while(true){ System.out.println("sb = " + sb); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } try { sb.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
在最后,wait()方法有两个重载的方法,他们的作用是让线程阻塞特定时间后还没有获得notify的通知,自动自己回收对象监视器。notify和notifyAll方法的却别是,notify是从全部等待的线程中随机选取一个通知它,notifyAll是通知全部在等待的线程。