Java 同步(Synchronization),等待(wait)通知(notify, notifyall)

Java关于同步,等待,通知

本文翻译Java语言规范中同步部分章节,翻译有问题请参考原文,本文仅在于自己理解Java的wait的原理。
原文链接(https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.2.1)

同步(Synchronization)

Java编程语言提供了多种线程间的通讯机制。最基本的方法就是同步(Synchronization),它通过监控器(Monitor)来实现。Java中每一个对象有一个监控器,线程可以锁定/解锁它。同一时间,仅一个线程可以持有监控器的锁。任何其他线程试图获得监控器的锁时会被阻塞,直到它们可以获得监控器的锁。线程可以锁定一个特殊的监控器多次,解锁仅反转一次锁定操作。

同步语句块试图在引用对象的监控器上执行一个锁动作,只有成功锁定,后续语句才会继续执行。当语句块执行完成时(无论正常还是异常),解锁动作自动在同步语句引用的对象上执行。

同步方法在调用时自动执行锁动作,方法体只有在锁成功获取后开始执行。如果方法是实例方法,实例相关的的监控器被调用者锁定(同一对象的多个同步方法共享同一个监控器对象,即this对象的监控器)。如果方法是静态方法,类对象的监控器被锁定。如果方法体执行完成(无论正常还是异常),解锁动作自动再相同的监控器对象上执行。

Java语言既不阻止也不要求死锁条件检测。线程直接或间接在多个对象上锁定时应该使用传统的死锁避免技术,如果需要,创建不会死锁的高级锁原语。

如读写volatile变量或使用java.util.concurrent包中的类提供了使用同步的可替代方法。

Wait集合和通知

每一个对象,除了拥有一个关联的监控器,还有一个等待集合。一个等待集合是一个线程的集合。

对象刚建立时,等待集合为空。从等待集合添加或删除线程的基本操作时原子性的。等待集合仅仅可以通过Object.wait, Object.notify, Object.notifyAll方法操作。

等待集合的操作会影响线程的中断状态,并且影响线程类处理中断的方法。另外,线程类的sleep和join方法有来自wait或notify动作的属性。

Wait

等待动作发生在wait()/wait(long millisecs)/wait(long millisecs)调用。用0参数调用wait(long millisecs), 或两个0参数调用wait(long millisecs, int nanosecs)时相当于wait()调用。

如果没有抛出InterruptedException中断异常,wait函数正常返回。

假设线程t在对象m上执行wait方法,并假设n为线程t在对象m上执行锁动作的数量。下列动作会发生:
- 如果n为零(例如,线程t没有拥有对象m的锁,即监控器对象),则一个IllegalMonitorStateException抛出
- 如果是一个带时间参数的wait调用,纳秒参数没有在0-999999范围,或毫秒参数为负值,则IllegalArgumentException抛出;
- 如果线程被中断,则一个InterruptedException抛出,并且中断状态设置为false.

  • 其他情况,下列顺序发生:

    1. 线程t添加到对象m的等待集合,并且在对象m上执行n个解锁动作。(这个时候线程会解锁对对象m的监控器对象的锁定)

    2. 线程t直到它从对象m的等待集合中移除时后续代码不会执行。线程会在下列任何一个动作后从等待集合中移除,并在接下来的某一时刻恢复运行:

  • 在对象m上,一个通知(notify)动作被执行,并且线程t被选中从等待集中移除。
  • 在对象m上,notifyAll动作被执行
  • 一个中断动作正在线程t上执行
  • 如果是带时间的等待调用,如果从wait超时,一个内部动作会从对象m等待集合中移除线程t。
  • 内部动作依赖于实现,实现允许但不鼓励去执行一个“虚假的唤醒”,即从等待集合删除线程,而没有清晰的指令唤醒线程。

注意,这个规定使得以下成为Java编码实践的必需:在循环中使用wait函数,并仅仅在线程所需要等待的逻辑条件满足后增止循环。

每一个线程必须决定从等待集合中删除事件的顺序。这个顺序没有必要和其他顺序一致,但是,线程必须表现得好像这些事件是按顺序发生一样。

例如,如果一个线程t在对象m的等待集合中,然后,线程t中断和对象m的一个通知同时发生,则这些事件必须有一个顺序。如果中断被认为先发生,则线程t将返回一个InterruptedException异常,并且对象m等待集合中的其他线程(如果存在的话)必须接收这个通知。如果通知被认为先发生,则线程t将从wait函数正常返回,并且中断仍然未决中。

  1. 线程t在对象m上执行n个锁动作

  2. 如果线程t在第2步中由于一个中断从对象m的等待集合中移除,则线程t的中断状态设置为false,并且wait函数抛出InterruptedException.

通知

Notification actions occur upon invocation of methods notify and notifyAll.
通知动作发生在notify和notifyAll方法调用时。

Let thread t be the thread executing either of these methods on object m, and let n be the number of lock actions by t on m that have not been matched by unlock actions. One of the following actions occurs:
假设线程t是对象m等待集合中的一个线程,并假设n为线程t在对象m上执行锁动作的数量。下列动作发生:

  • 如果n为0,则IllegalMonitorStateException异常抛出

    这有一种情况就是线程t没有拥有对象m的锁

  • 如果n>0, 并且有一个通知动作,如果对象m的等待集合不为空,对象m的等待集合中的一个线程u被选中的,并从等待集合中移除。

这儿没有保证哪个线程会被选中。从等待集合中移除线程u从wait动作中恢复。注意,无论如何,线程u的锁动作必须等到线程t完成从m的监控器中解锁后才可以成功恢复。


  • 如果n大于0, 并且notifyAll动作,所有的线程从对象m的等待集合中移除,并且恢复执行。

注意,无论如何,仅仅它们中的一个依次在wait恢复后可以锁定对象的监控器(Monitors)。

中断

中断动作在Thread.interrupt调用时发生,同样,在ThreadGroup.interrupt依次会调用定义的方法。

假设线程t调用某个线程u,u.interrupt,这里,t和u可能是用一个线程。这个动作造成线程u的中断状态设置为true.

另外,如果某个对象m的等待集合包含线程u,则线程u从对象m的等待集合中删除。这个使线程u从一个wait函数中恢复,这种情况下,它重新锁定对象m的监控器后,抛出一个中断异常(InterruptedException)

Thread.isInterrupted调用可以检查线程的中断状态。静态方法可以被一个线程调用以获得并清除它所拥有的中断状态。

等待,通知和中断的互动

上面的规范允许我们在使用等待,通知和中断交互时确定必须做的几个属性。

如果一个线程在等待时,通知被通知和中断,它可能:

  • 从等待中正常返回,并且还有一个未决的中断(换句话说,调用Thread.interrupted将返回true)
  • 从等待中返回一个中断异常(InterruptedException)

线程可能没有重置中断状态并从等待中返回。

相似地,通知不会因为中断而丢失。假设,对象m的线程等待集合s,另外一个线程在对象m上执行一个通知,则以下两个任何一个发生:
- 至少一个线程s必须从wait中正常返回;
- 等待集合s中所有的线程退出等待,并抛出中断异常

注意:如果一个线程同时被中断和通知唤醒,而这个线程被一个中断异常从等待中返回,则等待集合中的某一个线程必须被通知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值