The Java™ Tutorials — Concurrency :Guarded Blocks 保护块

The Java™ Tutorials — Concurrency :Guarded Blocks 保护块

原文地址:https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

关键点

  • wait():必须在synchronized块中使用,通过synchronized关键字过获取该对象的锁后,wait会使当前线程释放此锁并进入等待区,直到其他持有此对象的锁的线程调用了notify() 或者 notifyAll()时被唤醒。如果有其他线程也因此被唤醒,它们会重新竞争同步代码块。
  • 保护块:不断查询某条件,只有条件为真时,才继续执行,否则阻塞 
    • 常见写法:
//等待条件者
public synchronized void foo(){
while(!condition){
try{
wait();
} catch (Exception e){}
}
...
}
//提供条件者
public synchronized void foo2(){
....
notify() // or notifyAll();
}

全文翻译

Threads often have to coordinate their actions. The most common coordination idiom is the guarded block. Such a block begins by polling a condition that must be true before the block can proceed. There are a number of steps to follow in order to do this correctly.

线程不得不经常去协调它们的行为。最常见的协调手段就是“保护块”。这样的代码块会检查某种情况,且只有在这种情况为真时,代码块才会开始执行。你需要做以下步骤以保证可以正确地使用保护块。

Suppose, for example guardedJoy is a method that must not proceed until a shared variable joy has been set by another thread. Such a method could, in theory, simply loop until the condition is satisfied, but that loop is wasteful, since it executes continuously while waiting.

我们假设这样一种情况,在guardedJoy例子中,它是这样一个方法,在共享变量joy没有被其他线程设置之前,是不能够继续执行的。在理论上,这样的方法可以利用简单的循环直到条件被满足,但是这样的循环是十分浪费的,因为这会让程序在等待时也会不断地运行。

public void guardedJoy() {
// Simple loop guard. Wastes
// processor time. Don't do this!
while(!joy) {}
System.out.println("Joy has been achieved!");
}

A more efficient guard invokes Object.wait to suspend the current thread. The invocation of wait does not return until another thread has issued a notification that some special event may have occurred — though not necessarily the event this thread is waiting for:

一个更高效的保护块会通过调用Object.wait方法来阻塞当前的线程。wait方法直到其他线程发布一个某些事件可能已经发生的通知时,才会返回。虽然这个可能已发生的事件并非此线程正在等待的对象:

public synchronized void guardedJoy() {
// This guard only loops once for each special event, which may not
// be the event we're waiting for.
while(!joy) {
try {
wait();
} catch (InterruptedException e) {}
}
System.out.println("Joy and efficiency have been achieved!");
}

Note: Always invoke wait inside a loop that tests for the condition being waited for. Don’t assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true.

注意:在测试所等待条件的循环体之中,请务必加入wait。不要假定中断就一定来自你所等待的条件,或者条件一定为真。

Like many methods that suspend execution, wait can throw InterruptedException. In this example, we can just ignore that exception — we only care about the value of joy.

如同很多阻塞执行的方法,wait也可以抛出InterrruptedException。在这个案例下,我们可以简单的忽视这个异常,因为我们仅仅关注joy的值。

Why is this version of guardedJoy synchronized? Suppose d is the object we’re using to invoke wait. When a thread invokes d.wait, it must own the intrinsic lock for d — otherwise an error is thrown. Invoking wait inside a synchronized method is a simple way to acquire the intrinsic lock.

为什么这个版本的guardedJoy要被同步?假设d是我们正在使用的,用于调用wait方法的对象。当一个线程调用了d.wait时,它必须拥有d的内置锁,否则错误就会抛出。在同步方法内调用wait是获取此对象内置锁的一个简单方法。

When wait is invoked, the thread releases the lock and suspends execution. At some future time, another thread will acquire the same lock and invoke Object.notifyAll, informing all threads waiting on that lock that something important has happened:

但wait被调用后,线程就会释放锁,并阻塞执行。在未来的某个时间,另一个线程就会获取这把锁,并调用Object.notifyAll方法,从而通知所有等待此锁的线程某些重要事件发生了:

public synchronized notifyJoy() {
joy = true;
notifyAll();
}

Some time after the second thread has released the lock, the first thread reacquires the lock and resumes by returning from the invocation of wait.

在第二个线程释放此锁的后,第一个线程会获取此锁,并从调用的wait方法中返回以继续执行。

Note: There is a second notification method, notify, which wakes up a single thread. Because notify doesn’t allow you to specify the thread that is woken up, it is useful only in massively parallel applications — that is, programs with a large number of threads, all doing similar chores. In such an application, you don’t care which thread gets woken up.

这是一个附加的通知方法,notify。它只会唤醒一个线程。由于notify不允许你指定唤醒的线程,它仅在高并发环境中很有用。所谓高并发,就是指程序中含有大量的,执行相似的复杂任务的线程。在这样一个程序中,你就不会关心究竟要唤醒谁了。

Let’s use guarded blocks to create a Producer-Consumer application. This kind of application shares data between two threads: the producer, that creates the data, and the consumer, that does something with it. The two threads communicate using a shared object. Coordination is essential: the consumer thread must not attempt to retrieve the data before the producer thread has delivered it, and the producer thread must not attempt to deliver new data if the consumer hasn’t retrieved the old data.

让我们利用保护块来创建一个“生产者-消费者”程序。这类程序在两个线程中共享数据:生产者,负责产出数据;消费者,负责读取并利用数据。两线程通过同一个共享对象进行通信。此时,协同就变得非常重要了:消费者线程在数据没有被生产者发送前是不能执行读取操作的,并且生产者在消费者没有取走老数据时是不能够产出新数据的。

In this example, the data is a series of text messages, which are shared through an object of type Drop:

在本例中,数据是一些列文字信息。它们通过一个类型为Drop的对象被共享:

class Drop {
// Message sent from producer
// to consumer.
private String message;
// True if consumer should wait
// for producer to send message,
// false if producer should wait for
// consumer to retrieve message.
private boolean empty = true;

public synchronized String take() {
// Wait until message is
// available.
while (empty) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Toggle status.
empty = true;
// Notify producer that
// status has changed.
notifyAll();
return message;
}

public synchronized void put(String message) {
// Wait until message has
// been retrieved.
while (!empty) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Toggle status.
empty = false;
// Store message.
this.message = message;
// Notify consumer that status
// has changed.
notifyAll();
}
}

The producer thread, defined in Producer, sends a series of familiar messages. The string “DONE” indicates that all messages have been sent. To simulate the unpredictable nature of real-world applications, the producer thread pauses for random intervals between messages.

生产者线程于Producer类中被定义,它会发送一些列常见的信息。“DONE”表示所有的消息发送完毕。为了模拟真实环境下程序的不可预见性,生产者线程在消息间会进行随机时长的暂停。

class Producer implements Runnable {
private Drop drop;

public Producer(Drop drop) {
this.drop = drop;
}

public void run() {
String importantInfo[] = { "Mares eat oats", "Does eat oats",
"Little lambs eat ivy", "A kid will eat ivy too" };
Random random = new Random();

for (int i = 0; i < importantInfo.length; i++) {
drop.put(importantInfo[i]);
try {
Thread.sleep(random.nextInt(5000));
} catch (InterruptedException e) {
}
}
drop.put("DONE");
}
}

The consumer thread, defined in Consumer, simply retrieves the messages and prints them out, until it retrieves the “DONE” string. This thread also pauses for random intervals.

消费者线程,定义于Consumer类。它只是简单地将消息取出并打印出来,直到它获取到了“DONE”字符串。这个线程也会在几个消息间暂停随机的时间间隔。

import java.util.Random;

public class Consumer implements Runnable {
private Drop drop;

public Consumer(Drop drop) {
this.drop = drop;
}

public void run() {
Random random = new Random();
for (String message = drop.take();! message.equals("DONE");
message = drop.take()) {
System.out.format("MESSAGE RECEIVED: %s%n", message);
try {
Thread.sleep(random.nextInt(5000));
} catch (InterruptedException e) {}
}
}
}

Finally, here is the main thread, defined in ProducerConsumerExample, that launches the producer and consumer threads.

最后在ProducerConsumerExample定义的主线程中,会执行生产者和消费者线程。

public class ProducerConsumerExample {
public static void main(String[] args) {
Drop drop = new Drop();
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}

Note: The Drop class was written in order to demonstrate guarded blocks. To avoid re-inventing the wheel, examine the existing data structures in the Java Collections Framework before trying to code your own data-sharing objects. For more information, refer to the Questions and Exercises section.

注意:Drop类的创建是为了说明保护块。为了避免重复制造轮子,在你尝试定义你自己的数据共享对象时,请检查下Java集合框架中的数据结构是否可以满足需求。更多信息,参见《问题与练习》章节。

你可能感兴趣的:(thread,synchronized,Concurrent,wait,保护块)