【发布时间】:2011-05-14 01:11:15
【问题描述】:
我对等待和通知/通知所有有点困惑。
我知道每个 java 对象都有一个锁。我知道等待会释放其他线程的锁。 notify/notifyall 怎么样? notify/notifyAll 是否释放它为其他线程持有的锁?
【问题讨论】:
标签: java multithreading locking
我对等待和通知/通知所有有点困惑。
我知道每个 java 对象都有一个锁。我知道等待会释放其他线程的锁。 notify/notifyall 怎么样? notify/notifyAll 是否释放它为其他线程持有的锁?
【问题讨论】:
标签: java multithreading locking
不——notify/notifyAll 不会像wait 那样释放锁。直到调用notify的代码释放它的锁,被唤醒的线程才能运行。
Javadoc 是这么说的:
线程释放此监视器的所有权并等待,直到另一个线程通过调用 notify 方法或 notifyAll 方法通知在此对象的监视器上等待的线程唤醒。然后线程等待直到它可以重新获得监视器的所有权并恢复执行。
【讨论】:
synchronized (x) { x.notifyAll(); foo(); },则等待线程在foo() 完成之前无法运行。
})之前无法运行 - 当然是在foo()之后完成(或该块中的任何其他内容)。
我必须不同意那些说 notifyAll() 释放锁定的人,该对象上正在同步等待和通知线程。
一个例子:
Consumer 类包含一个块:
synchronized(sharedObject){
if(sharedObject.isReadyToConsume() == false){
sharedObject.wait();
}else {
sharedObject.doTheThing();
System.out.println("consumer consuming...");
}
}
场景:Consumer class 获得 sharedObject 对象的锁定,独占进入(它在同步块内)并看到 sharedObject 具有还没有准备好(没有什么可消耗的:)),它在 sharedObject 上调用wait() 方法。这样它就会释放锁(在那里停止执行!)并等待在另一个线程(可能是生产者)调用sharedObject.notify(); 或sharedObject.notifyAll(); 时通知继续。当它收到通知时,它会从 wait() 行继续
sharedObject 会跟踪要求通知它的线程。当某些线程调用 sharedObject.notifyAll() 方法时,sharedObject 会通知等待中的线程唤醒... 现在,棘手的部分是线程自然在到达其 synchronized(sharedObject){} 块的末尾时释放对象的锁。问题是如果我在该块中调用 notifyAll() 会发生什么? notifyAll() 唤醒等待中的线程,但锁仍归刚刚调用 notifyAll()
的线程所有看看Producer sn-p:
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 之后,“唤醒...”将被打印出来。事实并非如此……输出显示,在 Producer 退出其同步块后,Consumer 正在消费 sharedObject……
【讨论】:
wait() 告诉调用线程放弃监视器并进入睡眠状态,直到其他 线程进入同一个监视器并调用 notify()。
notify( ) 唤醒对同一对象调用 wait( ) 的线程。
notifyAll( ) 唤醒对同一对象调用 wait( ) 的所有线程。这 优先级最高的线程将首先运行。
【讨论】:
notify() 将唤醒 a 正在等待对象的线程,不一定第一个线程。
假设一堆读者想要读取某些资源的更新值,这将由 Writer 更新。那么 Reader 是如何知道 Writer 已经更新了 Resource Fields 的呢。
因此,为了在公共资源上的 Readers 和 Writers 之间同步这种情况,使用了 Object 类的三个 final 方法。
Wait :读者想要读取资源的更新值,他们注册资源对象,即当更新发生在同一对象上时,当 Writer 通知它时,读者将尝试获取资源锁定并读取更新资源。 - 等待仅在 Reader 具有锁定对象时调用,在我们的例子中它是资源。 - 一旦调用 wait 方法,Reader 就会释放 Lock Object。 - 现在只有同一个注册的对象(资源)阅读器才会收到通知信号。 - 如果 Reader 在 Object 上调用 wait,这与用于发送通知的 Object Writer 不同,Reader 将永远不会收到通知信号。 - 一旦读者收到通知,现在读者将尝试获取锁的内容(其中一个获得锁)读取资源的更新值。同样,其他 Reader 也轮流获取锁并读取更新的值。 - 一旦 Reader 读取到更新的值,执行业务逻辑并从同步块中出来,Reader 将释放锁,以便其他 Reader 可以获取它。
通知:Writer进入Synchronized Block,获取锁后执行其业务逻辑,更新资源Object,一旦资源Object更新,将通知正在等待的线程(Readers)锁。 - 仅向一个等待线程通知信号,由底层 Java 线程管理器决定 - 一旦 Writer 发出 notify() 信号,并不意味着 Reader 会立即读取更新值。首先 writer 必须释放 Lock,一旦它从 Synchronized 块中出来就会这样做。一旦 Lock 被释放并通知等待线程,然后 [In case of notify()] 通知线程将获取锁 [Released by Writer]然后进入同步块并从他离开的地方完成[即wait()之后的语句]。
Notify-All:在 notifyAll 中,所有注册了资源锁的线程都会收到通知。 - 一旦 notifyAll() 被触发,所有等待同一个锁的线程都将获得信号并准备好争用获取锁。 - 一旦 Writer 完成其 Job 并释放锁,任何一个 Reader 都将获得锁[哪个线程,再次由底层 Java 线程管理器实现决定]。 - 一旦阅读器获得锁,它将进入同步块,他离开的地方[即在 wait() 方法之后]执行它的任务,并在完成同步块时释放锁。 - 现在其他剩余线程将尝试获取锁,其中任何一个都将获得它,进入同步块,完成其任务然后释放锁。 - 这个过程将一直持续到所有注册读者完成那里的工作。
现在我们将看到它的代码。此外,我们还将讨论代码。 :
代码基本概述:它由三个类组成
资源.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();
}
}
代码观察:
【讨论】:
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() 不会释放锁。看看输出,Producing value 和 Consuming value 没有交替打印,即分别打印。
因此,notify/notifyAll 不会释放锁
阅读更多:http://javarevisited.blogspot.com/2015/07/how-to-use-wait-notify-and-notifyall-in.html#ixzz57kdToLX6
【讨论】:
为了澄清我的理解并提供一个示例以显示何时释放锁,我在调用 notify()/NotifyAll() 之后在以下代码中添加了打印语句:
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 的容器中出来。让我们看看..
notify():如果对象容器有多个处于等待状态的线程,那么在这个对象上调用 notify() 会给一个线程继续执行的机会。但是在退出等待状态后,线程仍然必须竞争对象监视器,如果成功获得监视器,它会继续执行,否则线程将返回等待状态。所以 notify() 也必须从同步块中调用。如果 notify() 不是从同步上下文中调用的,那么它会抛出 IllegalMonitorStateException。
notifyAll(): 在 Object 上调用 notifyAll() 可以确保 Object 容器中的所有线程都被唤醒,但是一旦被唤醒,它们就必须相互竞争或者任何其他线程想要获取对象监视器。哪个线程成功继续执行,其他线程必须回到等待状态并在对象容器中安顿下来。与 notify() 一样,notifyAll() 也应该在同步上下文中调用。
【讨论】:
在对象上调用notify() 方法会更改等待线程的状态。
通知线程仅在完成对要释放的锁对象的同步代码执行后才释放锁。
所以它是这样的:
等待()
如果一个线程在一个对象上调用wait() 方法,该线程会立即释放该对象的锁并进入等待状态。
通知()
但是当一个线程在一个对象上调用notify() 方法时,如果线程还有一些工作要做(即在 notify() 调用之后执行的代码),线程不会立即释放该对象的锁。如果同步代码执行完毕或者notify()之后没有语句,则线程释放锁,让被唤醒的线程从等待状态中释放。
【讨论】: