`

【解惑】 正确理解线程等待和释放(wait/notify)

阅读更多

对于初学者来说,下面这个例子是一个非常常见的错误。

/**
 *  线程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

 

    在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,当前线程等待。 换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。

    当前线程必须拥有此 对象监视器 该线程发布对此监视器的所有权并等待直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。 然后该线程将等到重新获得对监视器的所有权后才能继续执行。  

    对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:

                                    synchronized (obj) {
                                           while (<condition does not hold>)
                                                 obj.wait();
                                           // Perform action appropriate to condition
                                     }

     此方法只应由作为此对象监视器的所有者的线程来调用。

     抛出: IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。

               InterruptedException - 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态 被清除。

 

 

    看完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 虚拟机体系结构 》中堆内存区的内容,就会有更加深刻的理解了。

分享到:
评论
14 楼 763691 2010-08-05  
最近在研究线程 
13 楼 mwei 2010-02-27  
rain2005 写道
wait不是改变锁的状态,是把当前线程放到锁的等待队列里面,notify就是从锁的等待队列里面选择第一个等待的线程进行调度。每个对象都有一个唯一的锁。

wait, notify操作首先保证当前线程持有锁。


对于上面我有不观点:notify就是从锁的等待队列里面选择第一个等待的线程进行调度。
我认为是是从锁的等待队列里面唤醒一个线程,唤醒哪个线程是不确定的或任意的;如果等待队列里面只有一个线程,那么就会唤醒这个线程。这里的唤醒与sleep并不是对等关系。唤醒之后的线程进入就绪队列,等待被选择执行。
12 楼 Heart.X.Raid 2009-12-30  
xzqttt 写道
很明显的错误,oa和ob是两个对象



我在多线程方面不熟,很多时候会犯低级错误。

不过oa,ob为什么是两个对象。奇怪了,他们引用的都是lock吗?这个我确定,不用再讨论了。

不过感谢大家的讨论,这个问题我现在清楚不少了,呵呵,谢谢

还有:我都两次被评新手帖了,这到没什么。主要问题是每次被评为新手帖JavaEye都要我回答一次破问卷,才能重新发帖。好烦哪?

JavaEye是不错的网站,但好东西是要简单的。规矩多了,不是好事
11 楼 sw1982 2009-12-30  
zhaomiaojun 写道
对于锁的问题,关键是知道锁在谁手上
定义了一个synchronized 对象,意义上是把这个作为多线程之间的一把锁,
让线程wait,并不意味让这个对象wait
所以要释放正确的锁


这个观点比较认同,呵呵。 锁的是资源,不是线程类
10 楼 zhaomiaojun 2009-12-30  
对于锁的问题,关键是知道锁在谁手上
定义了一个synchronized 对象,意义上是把这个作为多线程之间的一把锁,
让线程wait,并不意味让这个对象wait
所以要释放正确的锁
9 楼 linliangyi2007 2009-12-30  
楼主乱来,你同步的是一个对象,notify的却是线程对象本身。
8 楼 xzqttt 2009-12-30  
很明显的错误,oa和ob是两个对象
7 楼 moonranger 2009-12-30  
Object lock=new Object(); //公共数据区 
ThreadA threada=new ThreadA(lock); 
ThreadB threadb=new ThreadB(lock); 

怎么不是同一个监视器?明明就是嘛
6 楼 lvgang 2009-12-30  
lz 提出的 Thread.currentThread().wait();改成oa.wait(),notify();改成 ob.notify() 貌似不能完成 lz 期望的线程 a 和线程 b 之间的交互吧,它们使用的不是同一个监视器
5 楼 Heart.X.Raid 2009-12-29  
3楼将很对,谢谢
4 楼 ftj20003 2009-12-29  
   个人认为: ThreadA中Thread.currentThread()返回当前线程对象的引用,所以wait()对应的对象隐式锁是threada实例的,ThreadB中的notify()隐含的参数this指向其实例,所以notify()对应的对象隐式锁是threadb的,synchronized(oa|ob)对应的对象隐式锁都是lock的,所以这段程序其实是三个不相干的隐式锁混在一起.
   而前两个对象的隐式锁并没有被任何的线程获得,synchronized的只是lock,两个线程竞争的都是lock的隐式锁.so,不管是wait()还是notify(),当前线程都不是其对应对象监视器的持有者,都会抛出上述的异常.
3 楼 rain2005 2009-12-29  
wait不是改变锁的状态,是把当前线程放到锁的等待队列里面,notify就是从锁的等待队列里面选择第一个等待的线程进行调度。每个对象都有一个唯一的锁。

wait, notify操作首先保证当前线程持有锁。
2 楼 Heart.X.Raid 2009-12-29  
呵呵,大概看懂了1楼的意思。

锁确实是可重入的,线程能够重复获得它已经拥有的锁。而且锁对象会维护一个持有计数(hold count)来追踪对lock方法的嵌套调用。
1 楼 sevenduan 2009-12-29  
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.

相关推荐

    java sleep和wait的解惑.docx

    `Object.wait()` 方法使得当前线程等待,直到其他线程调用此对象的 `notify()` 或 `notifyAll()` 方法。调用 `wait()` 方法的线程必须先获得该对象的锁。 ##### 1. 功能特点 - **线程状态变化**:当调用 `wait()` ...

    最新版的Java-解惑

    学习如何创建和管理线程,理解线程同步(如synchronized关键字、锁对象和wait/notify机制),以及线程池的使用,对于开发高效的并发应用程序至关重要。 5. **网络编程**:Java的Socket编程允许开发者创建网络应用。...

    Java解惑(中文).pdf

    6. **多线程**:Java提供了Thread类和Runnable接口来创建和管理线程,理解线程同步和并发控制(如synchronized关键字、wait/notify机制)是并发编程的基础。 7. **垃圾回收**:Java的自动内存管理依赖于垃圾回收...

    Java解惑.pdf

    线程同步和互斥的概念,如synchronized关键字和wait/notify机制,是多线程编程中的重要知识点。 6. **输入输出流**:Java的IO流系统允许程序进行数据的读写操作,包括文件操作、网络通信等。理解流的概念,熟悉...

    面试题解惑系列(十)——话说多线程

    理解和掌握多线程是Java开发者必备的技能之一,不仅要在理论层面上理解并发和线程的概念,还需要熟悉Java提供的各种线程同步机制和线程池的使用,以便在实际开发中解决并发问题,提高程序的稳定性和效率。...

    JAVA面试题解惑系列.rar

    5. **多线程**:线程的创建方式(Thread类和Runnable接口),线程同步(synchronized、wait/notify、Lock接口),以及死锁、活锁和饥饿现象的避免。 6. **IO与NIO**:传统IO流的使用,字符流与字节流,缓冲流,以及...

    JAVA面试题解惑系列

    6. **多线程**:理解线程的基本概念,如Runnable接口和Thread类,以及同步机制(synchronized关键字、wait/notify、Lock接口)。 7. **IO流**:熟悉字节流和字符流,以及缓冲流、转换流、对象流的使用,理解NIO...

    java解惑.doc

    本文档“java解惑.doc”旨在帮助Java开发者解决他们在学习和实践过程中遇到的问题,深入理解Java的核心概念和技术。 1. **基础语法与数据类型** Java的基础语法包括变量声明、条件语句(if-else、switch-case)、...

    JAVA解惑.rar

    5. **多线程编程**:Java提供了丰富的多线程支持,包括Thread类、Runnable接口、同步机制(synchronized关键字、wait/notify等),以及ExecutorService和Future接口,这些内容有助于你构建并发应用程序。 6. **输入...

    JAVA面试题解惑系列.zip

    5. **多线程**:线程的创建、同步机制(synchronized、wait/notify、Lock接口),以及并发工具类(ExecutorService、Semaphore、CountDownLatch等)。面试中可能会设计到并发问题的解决方案。 6. **设计模式**:...

    java 解惑,很不错。

    6. **多线程**:线程的基本概念,线程同步与通信(synchronized关键字、wait/notify、Thread.join()等),线程池的使用。 7. **IO流**:输入输出流的分类与使用,包括字符流和字节流,以及NIO(非阻塞I/O)的介绍。...

    2010年-Java解惑(中文)

    - 线程同步:synchronized关键字、wait()、notify()和notifyAll()方法防止并发访问引发的问题。 - 线程池:ExecutorService和ThreadPoolExecutor的理解与使用。 6. **I/O流**: - 字节流和字符流:理解它们的...

    JAVA解惑.pdf

    理解和掌握并发控制(如synchronized关键字、wait()和notify()方法)、线程同步(如Semaphore、CyclicBarrier)以及线程池(ExecutorService)的使用,对于开发高并发应用至关重要。 4. **集合框架**:Java集合框架...

    JAVA面试题解惑系列 (我感觉还是有用的)

    2. 多线程:了解线程的基本概念,如线程的创建、同步机制(synchronized关键字、wait/notify、Lock接口等),以及死锁、活锁和饥饿现象。 3. 异常处理:理解异常体系,掌握try-catch-finally的使用,以及如何自定义...

    Java解惑PPT8

    解决这类问题通常需要理解Java内存模型和线程同步机制,例如`synchronized`关键字、`wait()`, `notify()`和`notifyAll()`方法的使用。 **Puzzle 78 反射的污染** 这个谜题揭示了Java访问控制的边界。即使`hashCode...

    JAVA解惑疑难解答

    了解线程同步机制,如synchronized关键字、wait()、notify()和notifyAll()方法,以及Lock接口,能帮助开发者构建高效的并发程序。 4. **集合框架**:Java集合框架包括List、Set、Queue和Map接口,以及它们的实现类...

Global site tag (gtag.js) - Google Analytics