我们都知道,为了调用Object.wait()
,必须将此调用放置在同步块中,否则将IllegalMonitorStateException
引发。但是,进行此限制的原因是什么?我知道这wait()
会释放监视器,但是为什么我们需要通过使特定块同步来显式获取监视器,然后通过调用来释放监视器wait()
?
如果可以wait()
在同步块之外调用并保留其语义-挂起调用者线程,可能造成什么损害?
我们都知道,为了调用Object.wait()
,必须将此调用放置在同步块中,否则将IllegalMonitorStateException
引发。但是,进行此限制的原因是什么?我知道这wait()
会释放监视器,但是为什么我们需要通过使特定块同步来显式获取监视器,然后通过调用来释放监视器wait()
?
如果可以wait()
在同步块之外调用并保留其语义-挂起调用者线程,可能造成什么损害?
Answers:
wait()
只有在还存在时,A 才有意义notify()
,因此它始终与线程之间的通信有关,并且需要同步才能正常工作。有人可能会争辩说,这应该是隐式的,但实际上并没有帮助,原因如下:
从语义上讲,您永远不会wait()
。您需要满足一些条件,如果不是,请等到满足。所以你真正要做的是
if(!condition){
wait();
}
但是条件是由单独的线程设置的,因此为了正确执行此工作,您需要同步。
还有其他一些问题,只是因为线程退出等待并不意味着您要寻找的条件是正确的:
您可能会得到虚假的唤醒(这意味着线程可以从等待中唤醒,而从未收到通知),或者
可以设置条件,但是第三个线程在等待线程唤醒(并重新获取监视器)时再次使条件变为假。
为了处理这些情况,您真正需要的始终是这种变化:
synchronized(lock){
while(!condition){
lock.wait();
}
}
更好的是,根本不要弄乱同步原语,而要使用java.util.concurrent
软件包中提供的抽象。
Thread.interrupted()
。
如果可以
wait()
在同步块之外调用并保留其语义-挂起调用者线程,可能造成什么损害?
让我们wait()
用一个具体的例子来说明如果在同步块之外调用该函数会遇到什么问题。
假设我们要实现一个阻塞队列(我知道,API中已经有一个队列了:)
第一次尝试(没有同步)可能看起来像下面的样子
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
notify(); // Since someone may be waiting in take!
}
public String take() throws InterruptedException {
while (buffer.isEmpty()) // don't use "if" due to spurious wakeups.
wait();
return buffer.remove();
}
}
这是可能发生的情况:
使用者线程调用take()
并看到buffer.isEmpty()
。
在使用者线程继续调用之前wait()
,生产者线程会出现并调用full give()
,即buffer.add(data); notify();
使用者线程现在将调用wait()
(并且错过了notify()
刚刚被调用的线程)。
如果不幸,生产者线程将不会产生更多give()
的结果,因为消费者线程永远不会醒来,而且我们陷入了僵局。
一旦了解了问题,解决方案就显而易见了:用于synchronized
确保notify
在isEmpty
和之间不调用wait
。
无需赘述:同步问题是普遍的。正如Michael Borgwardt指出的那样,等待/通知完全是关于线程之间的通信的,因此您总是会遇到与上述情况类似的竞争状态。这就是为什么执行“仅在同步中等待”规则的原因。
您需要绝对保证服务员和通知者就谓词的状态达成一致。服务员在进入睡眠之前的某个时候会稍稍检查谓词的状态,但是它的正确性取决于谓词在进入睡眠时是正确的。这两个事件之间存在一段时间的漏洞,这可能会破坏程序。
在上面的示例中,生产者和消费者需要达成共识的谓词buffer.isEmpty()
。通过确保等待和通知在synchronized
块中执行来解决协议。
这篇文章已被重写为此处的文章:Java:为什么必须在同步块中调用wait
return buffer.remove();
在while之后添加while块wait();
,它可以工作吗?
wait
返回后缓冲区也可能为空。
Thread.currentThread().wait();
在main
try-catch包围的函数中InterruptedException
。没有synchronized
障碍,它给了我同样的例外IllegalMonitorStateException
。是什么使它现在达到非法状态?它synchronized
虽然在块内工作。
@Rollerball是正确的。将wait()
被调用,从而使线程可以等待某些条件时,这种情况发生wait()
调用发生时,线程被迫放弃其锁。
要放弃某些东西,您需要先拥有它。线程需要首先拥有锁。因此,需要在synchronized
方法/块内调用它。
是的,如果您未检查synchronized
方法/模块中的条件,则我确实同意上述所有关于潜在损坏/不一致的答案。但是,正如@ shrini1000所指出的那样,仅wait()
在同步块内调用不会避免这种不一致的发生。
如果您之前不同步,wait()
则可能导致的问题如下:
makeChangeOnX()
并检查while条件,则它是true
(x.metCondition()
return false
,Mean x.condition
is false
),因此它将进入它的内部。然后在该wait()
方法之前,另一个线程转到setConditionToTrue()
并将其设置x.condition
为true
和notifyAll()
。wait()
方法(不受notifyAll()
前一瞬间发生的影响)。在这种情况下,第一个线程将继续等待另一个线程执行setConditionToTrue()
,但这可能不会再次发生。但是,如果将
synchronized
更改对象状态的方法放在前面,则不会发生这种情况。
class A {
private Object X;
makeChangeOnX(){
while (! x.getCondition()){
wait();
}
// Do the change
}
setConditionToTrue(){
x.condition = true;
notifyAll();
}
setConditionToFalse(){
x.condition = false;
notifyAll();
}
bool getCondition(){
return x.condition;
}
}
我们都知道,wait(),notify()和notifyAll()方法用于线程间通信。为了消除丢失的信号和虚假的唤醒问题,等待线程始终在某些情况下等待。例如-
boolean wasNotified = false;
while(!wasNotified) {
wait();
}
然后通知线程将wasNotified变量设置为true并进行通知。
每个线程都有其本地缓存,因此所有更改都首先写入那里,然后逐渐提升到主内存。
如果未在同步块内调用这些方法,则wasNotified变量将不会刷新到主内存中,并且会存在于线程的本地缓存中,因此尽管已通过通知线程将其重置,但等待线程仍将继续等待该信号。
为了解决这些类型的问题,总是在同步块内部调用这些方法,以确保在同步块启动时,所有内容将从主存储器中读取,并在退出同步块之前被刷新到主存储器中。
synchronized(monitor) {
boolean wasNotified = false;
while(!wasNotified) {
wait();
}
}
谢谢,希望能弄清楚。
这基本上与硬件体系结构(即RAM和缓存)有关。
如果不synchronized
与wait()
或一起使用notify()
,则另一个线程可以输入相同的块,而不必等待监视器输入它。此外,例如在访问没有同步块的数组时,另一个线程可能看不到它的变化...实际上,当另一个线程在x级缓存中已经具有该数组的副本时,另一个线程将看不到它的任何变化(也称为线程处理CPU内核的第1 / 2nd / 3rd级缓存。
但是,同步块只是奖牌的一面:如果您实际上是从非同步上下文访问同步上下文中的对象,则即使在同步块中,该对象也不会同步,因为它拥有自己的副本。对象在其缓存中。我在此处写过有关此问题的文章:https : //stackoverflow.com/a/21462631,当锁包含非最终对象时,该对象的引用是否仍可以由另一个线程更改?
此外,我相信X级缓存是造成大多数不可复制的运行时错误的原因。这是因为开发人员通常不会学习底层知识,例如CPU的工作方式或内存层次结构如何影响应用程序的运行:http : //en.wikipedia.org/wiki/Memory_hierarchy
为什么编程类不首先从内存层次结构和CPU架构开始仍然是一个谜。“ Hello world”在这里无济于事。;)
直接从这个 Java的甲骨文教程:
当线程调用d.wait时,它必须拥有d的固有锁-否则将引发错误。在同步方法中调用等待是获取内部锁的一种简单方法。
根据文档:
当前线程必须拥有该对象的监视器。线程释放此监视器的所有权。
wait()
方法仅表示释放对象上的锁。因此,对象将仅在同步块/方法内被锁定。如果线程在同步块之外,则意味着它未被锁定;如果未锁定,那么您将在对象上释放什么?
线程在监视对象(同步块使用的对象)上等待,单个线程的整个行程中可以有n个监视对象。如果线程在同步块外部等待,则没有监视对象,并且其他线程也通知要访问监视对象,因此同步块外部的线程将如何知道已被通知。这也是wait(),notify()和notifyAll()在对象类而不是线程类中的原因之一。
基本上,监视对象是所有线程的公用资源,并且监视对象只能在同步块中可用。
class A {
int a = 0;
//something......
public void add() {
synchronization(this) {
//this is your monitoring object and thread has to wait to gain lock on **this**
}
}