该帖已经被评为新手帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-12-29
最后修改:2010-06-17
对于初学者来说,下面这个例子是一个非常常见的错误。 /** * 线程A: 循环50次后等待并放弃锁,让线程B执行。 */ class ThreadA extends Thread{ //线程同步的公共数据区 Object oa=null; ThreadA(Object o){ this.oa=o; } //线程A执行逻辑 public void run(){ //线程同步区域,需要申请公共数据的锁 synchronized(oa){ System.out.println("ThreadA is running......"); for(int i=0;i<100;i++){ System.out.println(" ThreadA value is "+i); if(i==50){ try { //当前线程等待 Thread.currentThread().wait(); } catch (InterruptedException e) { e.printStackTrace(); } }//if(i==50) }//for(int i) } } } /** * 线程B:等待线程A放弃锁,然后获得锁并执行,完成后唤醒线程A */ class ThreadB extends Thread{ //线程同步的公共数据区 Object ob=null; ThreadB(Object o){ this.ob=o; } //线程B执行逻辑 public void run(){ //线程同步区域,需要申请公共数据的锁 synchronized(ob){ System.out.println("ThreadB is running......"); for(int i=0;i<50;i++){ System.out.println(" ThreadB value is "+i); } //唤醒等待的线程 notify(); } } } //测试 public class ThreadTest { public static void main(String[] args){ Object lock=new Object(); //公共数据区 ThreadA threada=new ThreadA(lock); ThreadB threadb=new ThreadB(lock); threada.start(); //线程A执行 threadb.start(); //线程B执行 } } 程序很简单,就是让线程A,B交替打印。但是运行的时候会抛出两个异常: Exception in thread "Thread-0" java.lang.IllegalMonitorStateException: current thread not owner Exception in thread "Thread-1" java.lang.IllegalMonitorStateException: current thread not owner
问题就处在ThreadA中的Thread.currentThread().wait(); 和ThreadB中的notify();上。
初学者理解wait()的时候都认为是将当前线程阻塞,所以Thread.currentThread().wairt();视乎很有道理。但是不知道大家有没有发现,在JDK类库中wait()和notify()方法并不是Thread类的,而是Object()中的。我们仔细看看wait方法的JDK文档:
public final void wait() throws InterruptedException
在其他线程调用此对象的 当前线程必须拥有此
对象监视器
。该线程发布对此监视器的所有权并等待
,直到其他线程通过调用 对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用: synchronized (obj) { 此方法只应由作为此对象监视器的所有者的线程来调用。 抛出:
看完JDK文档以后,很显然,只要把开始的程序中Thread.currentThread().wait();改成oa.wait() 。 notify();改成 ob.notify()就没有问题了。
也就是说,只能通过同步块obj来调用wait/notify方法 ,而不能通过想当然的线程调用这两个方法。至于为什么是这样,我有一种想法,大家可以一起讨论一下:
首先,我们都知道JVM会给每一个对象都分配唯一的一把锁。这把锁是在对象中的。 然后,当Thread-0线程获得了这把锁后,应该是在对象中的锁内记录下当前占有自己的线程号,并把自己设置为已被占用。那么当Thread-0需要放弃锁的时候,锁对象会把 Thread-0放入到锁的等待队列中 。而这一切和Thread-0是没有任何关系的。自然也轮不到Thread-0对象来调用某个方法来改变另一个对象中的锁(这一点也说不通,我自己的锁凭什么让你来改)。 因此,也就出现用改变公共数据区对象的锁的方法是通过共数据区对象本省来调用,和线程对象是没有关系的。
事实上,每一个同步锁lock下面都挂了几个线程队列,包括就绪(Ready)队列,等待(Waiting)队列等。当线程A因为得不到同步锁lock,从而进入的是lock.ReadyQueue(就绪队列),一旦同步锁不被占用,JVM将自动运行就绪队列中的线程而不需要任何notify()的操作。但是当线程A被wait()了,那么将进入lock.WaitingQuene(等待队列),同时如果占据的同步锁也会放弃。而此时如果同步锁不唤醒等待队列中的进程(lock.notify()),这些进程将永远不会得到运行的机会。
就绪队列和等待队列有很大的不同,这一点要牢记。
对于object的wait、notify、notifyAll的理解,可以参见《Java 虚拟机体系结构 》中堆内存区的内容,就会有更加深刻的理解了。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-12-29
最后修改:2009-12-30
Nice article.
there is only reentrant lock in java, it means that there lock holder thread could acquire this lock again, then lock will increase his count (state+1) besides keeping lock's holder is current thread. |
|
返回顶楼 | |
发表时间:2009-12-29
呵呵,大概看懂了1楼的意思。
锁确实是可重入的,线程能够重复获得它已经拥有的锁。而且锁对象会维护一个持有计数(hold count)来追踪对lock方法的嵌套调用。 |
|
返回顶楼 | |
发表时间:2009-12-29
wait不是改变锁的状态,是把当前线程放到锁的等待队列里面,notify就是从锁的等待队列里面选择第一个等待的线程进行调度。每个对象都有一个唯一的锁。
wait, notify操作首先保证当前线程持有锁。 |
|
返回顶楼 | |
发表时间:2009-12-29
最后修改:2009-12-29
个人认为: ThreadA中Thread.currentThread()返回当前线程对象的引用,所以wait()对应的对象隐式锁是threada实例的,ThreadB中的notify()隐含的参数this指向其实例,所以notify()对应的对象隐式锁是threadb的,synchronized(oa|ob)对应的对象隐式锁都是lock的,所以这段程序其实是三个不相干的隐式锁混在一起.
而前两个对象的隐式锁并没有被任何的线程获得,synchronized的只是lock,两个线程竞争的都是lock的隐式锁.so,不管是wait()还是notify(),当前线程都不是其对应对象监视器的持有者,都会抛出上述的异常. |
|
返回顶楼 | |
发表时间:2009-12-29
3楼将很对,谢谢
|
|
返回顶楼 | |
发表时间:2009-12-30
lz 提出的 Thread.currentThread().wait();改成oa.wait(),notify();改成 ob.notify() 貌似不能完成 lz 期望的线程 a 和线程 b 之间的交互吧,它们使用的不是同一个监视器
|
|
返回顶楼 | |
发表时间:2009-12-30
Object lock=new Object(); //公共数据区
ThreadA threada=new ThreadA(lock); ThreadB threadb=new ThreadB(lock); 怎么不是同一个监视器?明明就是嘛 |
|
返回顶楼 | |
发表时间:2009-12-30
很明显的错误,oa和ob是两个对象
|
|
返回顶楼 | |
发表时间:2009-12-30
楼主乱来,你同步的是一个对象,notify的却是线程对象本身。
|
|
返回顶楼 | |