确实通知/notifyall释放正在持有的锁



我对等待和通知/notifyAll有点困惑。

我知道每个java对象都有一个锁。我知道等待会解除对其他线程的锁定。通知/通知怎么样?notify/notifyAll是否释放它为其他线程持有的锁?

否--notify/notifyAll不会像wait那样释放锁。在调用notify的代码释放锁之前,被唤醒的线程无法运行。

这就是Javadoc所说的:

线程释放此监视器的所有权,并等待,直到另一个线程通过调用notify方法或notifyAll方法通知在该对象监视器上等待的线程唤醒。然后线程等待,直到它能够重新获得监视器的所有权并恢复执行。

  • wait()告诉调用线程放弃监视器并进入睡眠状态,直到其他线程线程进入同一监视器并调用notify()。

  • notify()唤醒在同一对象上调用wait()的线程。

  • notifyAll()唤醒在同一对象上调用wait()的所有线程。这个优先级最高的线程将首先运行。

我不得不不同意有人说notifyAll()释放了正在同步等待和通知线程的对象上的锁

一个例子:

Consumer类包含一个块:

synchronized(sharedObject){
if(sharedObject.isReadyToConsume() == false){
sharedObject.wait();
}else {
sharedObject.doTheThing();
System.out.println("consumer consuming...");
}

}

场景:Consumer类获取sharedObject对象的锁,以独占方式进入(它在同步块内),并看到sharedObject[/strong>还没有准备好任何东西(没有要消费的东西:)),它调用共享对象上的wait()方法。这样,当另一个线程(Producer)调用sharedObject.notify();sharedObject.notifyAll();时,它会释放锁(停止执行!)并等待通知继续。当收到通知时,它会从wait()行继续

sharedObject跟踪要求通知它的线程。当某些线程调用sharedObject.notifyAll()方法时,sharedObject将通知等待唤醒的线程。。。现在,棘手的部分是,当线程到达其同步(sharedObject){}块的末尾时,它会自然地释放对象的锁。问题是如果我在那个块中调用notifyAll()会发生什么notifyAll()唤醒等待的线程,但锁仍然由刚刚调用notify All()TR的线程所有

查看Producer片段:

synchronized(sharedObject){
//We are exlusively working with sharedObject and noone can enter it
[... changing the object ...]
sharedObject.notifyAll();     //notifying the waiting threads to wake up
Thread.sleep(1000);           //Telling the current thread to go to sleep. It's holding the LOCK
System.out.println("awake...");

}

如果notifyAll()将释放锁,那么在Consumer类已经开始使用sharedObject之后,将打印出"唤醒…"。事实并非如此。。。输出显示在生产者退出其同步块后,消费者正在消费sharedObject。。。

  • wait()-释放锁并在收到通知时继续下一行
  • notify(),notifyAll()-不要释放锁。它们只是使等待的线程可以再次运行(而不是空闲)。当当前线程到达其同步块的末尾,并且线程scheduleder告诉他们锁已经被释放。为锁又开始了

假设一群读者想要阅读某个资源的更新值,该值将由Writer更新。然后读者如何知道资源字段已由作者更新。

因此,为了在公共资源上同步读者和作者之间的此类案例,使用了Object类的三种最终方法。

  • wait()
  • notify()
  • notifyAll()

等待:读者想要读取资源的更新值,他们向资源对象注册,即当更新发生在同一对象上时,当Writer通知它时,读者将尝试锁定资源并读取更新的资源。-等待只有当阅读器有锁定对象时才被调用,在我们的例子中它是资源。-一旦调用了wait方法,Reader就会释放Lock对象。-现在,只有同一个注册的对象(资源)阅读器才会收到通知信号。-如果Reader调用wait on Object,这与用于发送通知的Object Writer不同,那么Reader将永远不会得到通知信号。-一旦通知了读卡器,现在读卡器将尝试为锁(其中一个获得锁)读取资源的更新值。类似地,其他读卡器也轮流获取锁并读取更新后的值。-一旦读卡器读取更新后的值,在那里执行业务逻辑并从同步块中出来,读卡器就会释放锁,以便其他读卡器可以获得它。

通知:写入程序进入同步块,获取锁后执行其业务逻辑,更新资源对象,一旦资源对象更新,它将通知在同一锁上等待的线程(读卡器)。-只向一个等待线程发送Notify信号,该信号由底层Java线程管理器决定-一旦Writer发出notify()信号,并不意味着Reader会立即读取更新值。首先,编写器必须释放锁,一旦锁从同步块中出来,它就会释放锁。一旦释放了Lock并通知了等待线程,则[In case of notify()]通知的线程将获取Lock[Ireleased by Writer],然后进入Synchronized Block并从他离开的位置完成[即wait()之后的语句]。

Notify All:在notifyAll中,所有使用资源锁注册的线程都将获得通知。-一旦notifyAll()被触发,所有在同一个锁上等待的线程都将获得信号,并准备在争用中获取锁。-一旦Writer完成其作业并释放锁,任何一个Reader都将获得锁[哪个线程,同样由底层Java线程管理器实现决定]。-一旦读卡器获得锁,它将进入同步块,在他离开的地方[即wait()方法之后]执行任务,完成同步块后释放锁。-现在,其他剩余线程将尝试获取锁,他们中的任何人都将获得锁,进入同步块,完成任务,然后释放锁。-这个过程将一直持续到所有注册读者完成那里的工作。


现在我们将看到它的代码。此外,我们还将讨论代码。:

代码的基本概述:它由三类组成

  • 资源类:将在其上获取锁,并调用wait()和notify(),notifyAll()
  • ReaderTask:实现可运行的接口,暗示阅读器作业,想要读取资源对象的更新值
  • WriterTask:实现可运行接口,意味着编写器作业,将更新资源对象并通知已注册的等待线程
  • 演示类:它将创建3个读卡器和1个编写器线程,将各自的任务绑定到它们并启动线程

Resource.java

public class Resource {
private String mesg;
public void setMesg(String mesg){
this.mesg =mesg;
}
public String getMesg(){
return this.mesg;
}
}

WaitThreadTask.java

public class WaitThreadTask implements Runnable {
private Resource resource;
public WaitThreadTask(Resource resource){
this.resource = resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(resource){
System.out.println("Before Reading Updated Value By : " +Thread.currentThread().getName() );
//We need to Take care to get the updated value, so waiting for writer thread to update value.
try {
//Release resource Lock & wait till any notification from Writer.
resource.wait();
System.out.println("Waiting is Over For : "+ Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Read Updated Value
System.out.println("Updated Value of Resource Mesg :" + resource.getMesg() + " Read By :" +Thread.currentThread().getName());
}
}
}

WriterThreadTask.java

public class WriterThreadTask implements Runnable{
private Resource resource;
public WriterThreadTask(Resource resource){
this.resource = resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(resource){
System.out.println("Before Updating Resource By : " + Thread.currentThread().getName());
//Updating resource Object Message
resource.setMesg("Hi How are You !!!");
resource.notify();
//resource.notifyAll();
//Once Writer Comes Out from Synch Block, Readers will Content to read the values.
System.out.println("Task Done By Writer Thread.");
}
}
}

ThreadDemo.java

public class ThreadDemo {
public static void main(String args[]){
//Create Single Resource Object, which can act as Lock on Writer and Readers.
Resource lock = new Resource();
//Three Readers and One Writer runnable Tasks.
Runnable taskR1 = new WaitThreadTask(lock);
Runnable taskR2 = new WaitThreadTask(lock);
Runnable taskR3 = new WaitThreadTask(lock);
Runnable taskW1 = new WriterThreadTask(lock);
Thread t1 = new Thread(taskR1, "Reader1");
Thread t2 = new Thread(taskR2, "Reader2");
Thread t3 = new Thread(taskR3, "Reader3");
Thread t4 = new Thread(taskW1, "Writer1");
t1.start();
t2.start();
t3.start();
/*try{
Thread.sleep(5000);
} catch(InterruptedException e){
e.printStackTrace();
}*/
t4.start();
}
}

代码观察:

  • notify()/notifyAll()和wait():仅适用于它们已经获取的锁定对象。例如:Synchornised(ObjectA){……//…//ObjectB.wait()或ObjectB.notify()或ObjectB.notifyAll()…}则会抛出IllegalMonitorStateException。因此,在调用具有相同锁的上述三种方法之前,必须注意必须获取锁。即使你只是简单地写notify()、wait()或notifyAll(),它仍然会抛出IllegalMonitorStateException,因为[它建议必须在这个对象上获取锁,但事实并非如此]
  • 阅读器只能接收发送相同通知的信号。如果等待发生在与发送通知的对象不同的对象上,则读者将永远不会收到通知,因此他们将永远等待
  • 在Writer之前注册的读卡器可以发送通知,只有那些读卡器才会收到通知。因为如果Writer在读卡器注册到Object之前先发送通知,他们将不会收到信号,因为信号已经丢失:丢失的信号
  • 读写器应获取同一对象上的Lock,并应调用同一对象的等待/通知信号。如果上面的代码被修改为,而不是使用资源进行锁定和等待并通知,如果我们使用这个。会发生什么?好所有的读卡器都将永远等待,因为读卡器在WriterThreadTask上注册了不同的WaitThreadTask对象和写入器通知。因此,读卡器都不会接收到通知信号,因为它们注册为在各自的WaitThreadTask对象上接收信号,而不是在WriterThreadTask对象上
public class ProducerConsumerInJava {
public static void main(String args[]) {
System.out.println("How to use wait and notify method in Java"); 
System.out.println("Solving Producer Consumper Problem"); 
Queue<Integer> buffer = new LinkedList<>(); 
int maxSize = 10; 
Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
producer.start(); 
consumer.start(); 
}
}
class Producer extends Thread {
private Queue<Integer> queue; 
private int maxSize; 
public Producer(Queue<Integer> queue, int maxSize, String name){ 
super(name); this.queue = queue; this.maxSize = maxSize; 
}
public void run() { 
while (true) { 
synchronized (queue) { 
while (queue.size() == maxSize) { 
try { 
System.out .println("Queue is full, " +                         
"Producer thread waiting for " + "consumer to take 
something from queue"); 
queue.wait(); 
} catch (Exception ex) { 
ex.printStackTrace(); 
} 
}
Random random = new Random(); 
int i = random.nextInt(); 
System.out.println("Producing value : " + i); 
queue.add(i); 
queue.notifyAll(); 
} 
} 
} 
}
class Consumer extends Thread {
private Queue<Integer> queue; 
private int maxSize; 
public Consumer(Queue<Integer> queue, int maxSize, String name){ 
super(name); this.queue = queue; this.maxSize = maxSize; 
}
public void run() { 
while (true) { 
synchronized (queue) { 
while (queue.isEmpty()) { 
try { 
System.out .println("Queue is empty," +                         
"Consumer thread is waiting" +
" for producer thread to put something in queue");
queue.wait(); 
} catch (Exception ex) { 
ex.printStackTrace();
}
} 
System.out.println("Consuming value : " + queue.remove()); 
queue.notifyAll(); 
} 
} 
} 
}

这是消费者和生产者计划的一个例子。

上述程序执行后的输出如下所示:

How to use wait and notify 
method in Java Solving Producer Consumper Problem 
Queue is empty,Consumer thread is waiting for producer thread to put 
something in queue 
Producing value : -1692411980 
Producing value : 285310787 
Producing value : -1045894970 
Producing value : 2140997307 
Producing value : 1379699468 
Producing value : 912077154 
Producing value : -1635438928 
Producing value : -500696499 
Producing value : -1985700664 
Producing value : 961945684 
Queue is full, Producer thread waiting for consumer to take something from 
queue Consuming value : -1692411980 
Consuming value : 285310787 
Consuming value : -1045894970 
Consuming value : 2140997307 
Consuming value : 1379699468 
Consuming value : 912077154 
Consuming value : -1635438928 
Consuming value : -500696499 
Consuming value : -1985700664 
Consuming value : 961945684 
Queue is empty,Consumer thread is waiting for producer thread to put 
something  in queue 
Producing value : 118213849

因此,我们可以得出的结论是,notifyAll()或notify()不会释放锁。查看输出,生产值和消耗值不是交替打印的,即分别打印。

因此,notifyAll不会释放锁定

阅读更多:http://javarevisited.blogspot.com/2015/07/how-to-use-wait-notify-and-notifyall-in.html#ixzz57kdToLX6

为了澄清我的理解,并为所有人提供一个在释放锁时显示的示例,我在调用notify()/NotifyAll()后向以下代码添加了print语句:

class ThreadDemo {
public static void main(String[] args) {
Shared s = new Shared();
new Producer(s).start();
new Consumer(s).start();
}
}
class Shared {
private char c = 'u0000';
private boolean writeable = true;
synchronized void setSharedChar(char c) {
while (!writeable)
try {
wait();
} catch (InterruptedException e) {
}
this.c = c;
writeable = false;
notifyAll();
System.out.println("setSharedChar notify() called - still in synchronized block.");
}
synchronized char getSharedChar() {
while (writeable)
try {
wait();
} catch (InterruptedException e) {
}
writeable = true;
notifyAll();
System.out.println("getSharedChar notify() called - still in synchronized block.");
return c;
}
}
class Producer extends Thread {
private Shared s;
Producer(Shared s) {
this.s = s;
}
public void run() {
System.out.println("Starting producer thread.");
for (char ch = 'A'; ch <= 'Z'; ch++) {
System.out.println("Producer thread getting ready to create a char.");
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
s.setSharedChar(ch);
System.out.println(ch + " produced by producer.");
}
}
}
class Consumer extends Thread {
private Shared s;
Consumer(Shared s) {
this.s = s;
}
public void run() {
System.out.println("Starting consumer thread.");
char ch;
do {
System.out.println("Consumer thread getting ready to read a char.");
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
ch = s.getSharedChar();
System.out.println(ch + " consumed by consumer.");
} while (ch != 'Z');
}
}

当我运行这个例子足够多次时,有一点我最终看到了程序的输出:

...
F produced by producer.
Producer thread getting ready to create a char.
getSharedChar notify() called - still in synchronized block.
F consumed by consumer.
Consumer thread getting ready to read a char.
setSharedChar notify() called - still in synchronized block.
G produced by producer.
Producer thread getting ready to create a char.
getSharedChar notify() called - still in synchronized block.
setSharedChar notify() called - still in synchronized block.
G consumed by consumer.

由于输出getSharedChar能够在setSharedChar之前出现,因此锁似乎正在立即释放,或者不需要通过调用notifyAll()重新进入同步的getSharedChar()函数。锁可能仍然存在,但如果您可以在没有锁的情况下重新进入函数,有什么区别?我能够看到类似的输出用notify()代替notifyAll()。这是在64位Windows 7系统上的Java 1.7.0_15上完成的。

wait():实际上Java中的每个对象都有一个监视器,要进入任何同步块,线程必须首先获取这个监视器,然后只有他才能进入这个同步块。由于代码的关键部分每次由一个线程执行,所以它对应用程序的整体性能有很大影响。因此,可以要求线程离开关键部分并等待一段时间来代替资源(监视器)。为了实现这种行为,Java直接在Object类中提供了一个wait()api。

因此,每当线程遇到wait()API时,它都会丢弃当前监视器及其持有的所有其他监视器,并进入链接当前对象的等待状态。重要的是要理解,在线程首先获取监视器的对象的上下文中,它进入了等待状态。。在概念上,我解释说,每个对象都有一个容器库,所有等待的线程都放在那里。线程可以通过多种方式从Object的容器库中出来。让我们看看。。

  • 当另一个线程来了并响了一次铃时,在Java中通过调用notify()方法
  • 当另一个线程出现并多次响起时,其中一个线程有机会走出Object的集装箱屋。在Java中,我们可以通过对同一个对象调用notifyAll()来完成此操作
  • 如果我们有线索的参考资料在集装箱房里等待。对Thread对象调用interrupt()使其脱离等待状态并为其带来Object的异常块
  • 存在过载等待(长毫秒)和等待(长millSec,int nanos)方法。随着时间的推移,线程有资格到来退出等待状态并再次竞争对象监视器。万一线程在超时后无法获取监视器,因此它必须仅等待notify()调用

notify():如果对象容器中有多个线程处于等待状态,则在此对象上调用notify(。但在退出等待状态后,线程仍然必须争夺对象监视器,如果成功获得监视器,则继续执行,否则线程将返回等待状态。因此notify()也必须从同步块中调用。如果notify()不是从同步上下文调用的,那么它会引发IllegalMonitorStateException。

notifyAll():对Object调用notifyAl()可以确保Object容器库中的所有线程都被唤醒,但一旦被唤醒,它们就必须相互竞争,或者任何其他线程都想获取对象监视器。哪一个线程成功地继续执行,其他线程就必须回到等待状态,并在对象容器中安顿下来。与notify()一样,notifyAll()也应该在同步上下文中调用。

解释摘自http://coder2design.com/thread-communication/

在对象上调用notify()方法会更改等待线程的状态。通知线程只有在完成对要释放的锁对象的同步代码执行后才会释放锁。

它是这样的:

wait()如果线程在对象上调用wait()方法,线程将立即释放该对象的锁并进入等待状态。

notify()但是当一个线程对一个对象调用notify()方法时,如果该线程还有更多的工作要做(即notify()调用后要执行的代码),则该线程不会立即释放该对象的锁。如果同步代码的执行已经完成,或者notify()之后没有任何语句,那么线程就会释放锁,将线程从等待状态唤醒。

最新更新