Guarded Blocks

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.

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:

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.

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.

Why is this version of guardedJoy synchronized? Suppose d is the object we're using to invoke wait. When a thread invokesd.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.

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:

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.


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.

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:

public 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.

import java.util.Random;

public 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.

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.

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.

 
译文:
保护块儿
  线程之间经常需要协同工作。协同最多的就是保护块儿。这样的块儿会轮询一个条件直到这个条件为真才能继续往下执行。为了保证这个执行正确这里需要一些列的步骤。
  假设,例如guardedJoy方法必须轮询joy变量直到这个变量被另外一个线程设定值为止。这个方法,在理论上,会一直循环到满足条件为止,由于它会因等待而一直执行,因此这个循环是非常浪费的。
 public void guardedJoy() {
// Simple loop guard. Wastes
// processor time. Don't do this!
while(!joy) {}
System.out.println("Joy has been achieved!");
}

  一个更加有效率的保护块是在循环中执行Object.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!");
}


注意:在一个循环中始终执行wait方法在检测是否处于等待状态。不要假定中断一定会发生在你期待的情况下,可能在为真的情况下也出现。



  像许多执行挂起的方法一样,wait会抛出InterruptedException异常。在这个实例中我们可以忽视这个异常,我们主要是关心joy的值。

  为什么这个版本的guardedJoy是同步的?假设d是我们执行wait方法的对象。当一个线程执行d.wait()方法。对于d它一定拥有一个固定锁,否则就会抛出错。在一个同步方法中执行wait方法是获得固定锁的一种简单的方法。
  当wait方法执行了,这个线程线程会释放这个锁并挂起。在将来,其他的线程会获得这个锁并执行 Object.notifyAll方法。告诉所有的线程,一些重要的事情已经发生了。
 public synchronized notifyJoy() {
joy = true;
notifyAll();
}

许多时候当第二个线程释放了这个锁,第一个线程会重新获得锁等待返回调用。



注意:这里有第二个唤起注意的方法,notify,它唤起单个线程的注意。由于notify并不允许你指定你要唤起那个特定的线程,因此它的用处只是在大规模的并行程序中,即,在有许多线程的程序中,而且每个线程都做着简单且重要的事情,你并不关心那个线程是唤醒的。

  让我们用保护块儿构造一个生产者-消费者的应用程序。这个应用程序的两个线程会共享数据:生产者,创建数据;消费者,用这些数据做一些其他的事情。这两个线程通过共享一个对象来交流。协同工作是肯定的:在生产者传递数据之前,消费者线程必须一直检索新的数据,如果消费者没有检索旧的数据,那么生产者就不能传递新的数据。
  在这个实例中,数据是一系列的消息,通过Drop类共享来实现。
 public 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();
}
}

生产者线程,在producer类中定义,产生一系列相似的消息。“DONE”表示所有的消息都已经发送。为了模拟现实世界的生产者-消费者现象,生产者线程会在发送下一个消息的时候停留随机的时间。

 import java.util.Random;

 public 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");
}
}

消费者线程,在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) {}
}
}
}

最后,是主线程,在ProducerConsumerExample线程中定义,它载入了Producer和Consumer类。

 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();
}
}

注意:写Drop类是为了展示保护块儿。为了避免重复工作,在你编写共享数据类型的时候,请参考已经有的Java Collections Framework。想了解更多详细,请参看Questions and Exercises 节。


养养眼^-^

【翻译十四】java-并发之保护块儿的更多相关文章

  1. Java进阶(二十四)Java List集合add与set方法原理简介

    Java List集合add与set方法原理简介 add方法 add方法用于向集合列表中添加对象. 语法1 用于在列表的尾部插入指定元素.如果List集合对象由于调用add方法而发生更改,则返回 tr ...

  2. JDK源码阅读-------自学笔记(二十四)(java.util.LinkedList 再探 自定义讲解)

    一.实现get方法 1.一般思维实现思路 1).将对象的值放入一个中间变量中. 2).遍历索引值,将中间量的下一个元素赋值给中间量. 3).返回中间量中的元素值. 4).示意图 get(2),传入角标 ...

  3. 【翻译十八】java-并发之锁对象

    Lock Objects Synchronized code relies on a simple kind of reentrant lock. This kind of lock is easy ...

  4. Java 读书笔记 (十四) Java 方法

    finalize() 方法 finalize() 用来清除回收对象.  //为什么要回收内存?怎样写可以避免内存过多占用?什么时候需要手动回收内存? protected void finalize() ...

  5. Java学习笔记二十四:Java中的Object类

    Java中的Object类 一:什么是Object类: Object类是所有类的父类,相当于所有类的老祖宗,如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类默认继承Object ...

  6. 二十四 java 多线程一些知识点

    1:blocked线程和waiting的线程的区别? 如何唤醒? java线程中含有waiting与blocked两种状态: 线程的 blocked状态往往是无法进入同步方法/代码块来完成的(BLOC ...

  7. 菜鸡的Java笔记 第二十四 - java 接口的基本定义

    1.接口的基本定义以及使用形式        2.与接口有关的设计模式的初步认识        3.接口与抽象类的区别                 接口与抽象类相比,接口的使用几率是最高的,所有的 ...

  8. 【翻译十二】java-并发之活性

    A concurrent application's ability to execute in a timely manner is known as its liveness. This sect ...

  9. Gradle 1.12 翻译——第十四章. 教程 - 杂七杂八

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

随机推荐

  1. 极客DIY:制作一个可以面部、自主规划路径及语音识别的无人机

    引言 现在大部分无人机厂商都会为第三方开发者提供无人机API接口,让他们更容易地开发无人机飞行控制应用程序,让无人机想怎么玩就怎么玩.有的API接口可以帮助开发者开发基于Web版的APP.手机APP甚 ...

  2. 添加Mysql到Windows系统服务

    下载了免安装版的MySQL后,将压缩包加压.到这一步mysql还不能工作,我们还需要做一些工作: 1.     安装mysql服务 新建一个批处理文件StartMysql.bat,文件内容如下: @E ...

  3. ndk学习13: proc

    一.进程相关信息 /proc目录中包含许多以数字命名的子目录,这些数字表示系统当前正在运行进程的进程号,里面包含对应进程相关的多个信息文件 结构如下: 进程相关的信息如下: 部分信息如下 cmdlin ...

  4. POJ 3903

    http://poj.org/problem?id=3903 这个题目是一个求最长递增子序列,这个只是求长度而已,所以可以用LIS 所谓的LIS也就是用二分优化来减少时间而已,而且其只能求出最长的序列 ...

  5. the XMLHttpRequest Object

    Ajax的核心是XMLHttpRequest对象.XMLHttpRequest对象允许异步发送请求并且以文本的形式返回结果.发送什么样的请求(what),在服务器端怎样处理这个请求(how),返回什么 ...

  6. ios iphone6 Plus 的适配问题

    最近更新一个老程序,发现在iphone6 plus上用nslog打印出的screen 的frame还是320*568, 不是414*736, 查了一下,原来需要设置iphone6 plus的启动画面才 ...

  7. 多字段 java对象排序

    public class ReflexUtil { static Logger logger = LoggerFactory.getLogger(ReflexUtil.class); //getMet ...

  8. 8.js模式-状态模式

    1. 状态模式 var offLightState = function(light){ this.light = light; } offLightState.prototype.buttonWas ...

  9. 17. javacript高级程序设计-错误处理与调试

    1. 错误处理与调试 l 在可能发生错误的地方使用try-catch方法,可以对错误进行及时的相应 l 使用window.onerror事件处理程序,这种方式可以接受try-catch不能处理的所有错 ...

  10. Java for LeetCode 212 Word Search II

    Given a 2D board and a list of words from the dictionary, find all words in the board. Each word mus ...