`

【解惑】 正确理解线程等待和释放(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.

相关推荐

    麦肯锡 组织 概述与基本框架gl.ppt

    麦肯锡 组织 概述与基本框架gl.ppt

    node-v10.11.0-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    大型强子对撞机电源转换器设计与运行挑战

    大型强子对撞机电源转换器设计与运行挑战

    (优作)低功耗STM32F411开发板(原理图+PCB源文件+官方例程+驱动等)

    本文档提供了一套完整的STM32F411低功耗开发板资源,包含详细的原理图、PCB设计源文件、官方提供的示例程序以及必要的驱动程序。这些资料对于嵌入式系统开发者来说是宝贵的学习资源,特别适合那些希望深入了解STM32F411微控制器及其应用的学生、工程师和电子爱好者。文档旨在帮助用户快速上手STM32F411的开发工作,无论是进行学术研究、产品原型设计还是个人项目实践,都能从中获益。 关键词标签: STM32F411 低功耗 开发板 资料下载

    基于机器学习的发债主体违约风险预测python源码+项目说明+设计报告+答辩PPT.zip

    该项目以发债企业作为研究对象,利用财务逻辑和技术手段对178个原始特征指标进行有效筛选,构建了基于多种机器学习算法的模型,对比后挑选LightGBM模型作为最终模型进行更精细化训练,最终模型关键预测指标均有比较好的效果。 使用说明 BondDefault文件为项目代码 基于机器学习的发债主体违约风险预测.pdf为pdf形式的项目文稿 基于机器学习的发债主体违约风险预测.pptx为ppt形式的项目展示 由于数据集太大,此处没有上传

    Rain Birdt Simple To Set Timer (SST) 使用说明书.pdf

    Rain Birdt Simple To Set Timer (SST) 使用说明书

    SITRANS LVL 200S, LVL 200E 振动式安全手册.pdf

    SITRANS LVL 200S, LVL 200E 振动式安全手册

    麦肯锡-xx电信市场分析报告gl.ppt

    麦肯锡-xx电信市场分析报告gl.ppt

    基于matlab实现的三次样条插值法 求信号的包络线 源代码.rar

    基于matlab实现的三次样条插值法 求信号的包络线 源代码.rar

    麦肯锡_xx大客户培训战略报告gl.ppt

    麦肯锡_xx大客户培训战略报告gl.ppt

    node-v9.0.0-linux-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v8.6.0-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    嗨森无人机管理平台.zip

    无人机最强算法源码,易于部署和学习交流使用

    node-v8.16.2-linux-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

Global site tag (gtag.js) - Google Analytics