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. C++虚函数、虚继承、对象内存模型(转)

    参考:http://blog.csdn.net/hxz_qlh/article/details/14633361 需要注意的是虚继承.多重继承时类的大小.

  2. BestCoder Round #86 解题报告

    A.Price List Sol 求和查询 Code #include<cstdio> #include<algorithm> #include<iostream> ...

  3. 大数据热点问题TOP K

    1单节点上的topK (1)批量数据 数据结构:HashMap, PriorityQueue 步骤:(1)数据预处理:遍历整个数据集,hash表记录词频 (2)构建最小堆:最小堆只存k个数据. 时间复 ...

  4. 谷歌chrome浏览器和火狐firefox浏览器自带http抓包工具和请求模拟插件

    谷歌chrome浏览器自带http抓包工具 chrome://net-internals/ 谷歌chrome浏览器http请求模拟插件:postman 火狐http请求模拟插件:httprequest ...

  5. Android Studio (Gradle)编译错误

    Error:Execution failed for task ':app:processDebugResources' .com.android.ide.common.process.Process ...

  6. NetBeans常用快捷键

    Ctrl+Space:代码自动完成,在Windows下通常与输入法切换有冲突,我改成了ALT+2: Ctrl+/:注释&取消注释: Alt+Shift+F:编辑器自动格式,由于三个组合键不好按 ...

  7. vs版本转换工具

      [转]C#写的工程项目移植转换工具 – 支持VS2005/VS2010/VS2012/VS2013 经常用Visual Studio开发项目的是不是会经常遇到下面这种情况或者类似于这样的情况?用新 ...

  8. 线条围绕 div 中心转圈 效果

    1. 用到知识: CSS:animate  ,clipe 2.原理: 用clip 属性 将div  切边 ,会出现 切边的动态效果,然后内部的div 遮住外部的div  ,流出一部分 作为边: 就是旋 ...

  9. 【编程题目】有 4 张红色的牌和 4 张蓝色的牌,主持人先拿任意两张,再分别在 A、B、C 三人额头上贴

    第 22 题(推理):有 4 张红色的牌和 4 张蓝色的牌,主持人先拿任意两张,再分别在 A.B.C 三人额头上贴任意两张牌,A.B.C 三人都可以看见其余两人额头上的牌,看完后让他们猜自己额头上是什 ...

  10. 51nod 1070 Bash游戏 V4 (斐波那契博弈)

    题目:传送门. 有一堆个数为n(n>=2)的石子,游戏双方轮流取石子,规则如下: 1)先手不能在第一次把所有的石子取完,至少取1颗: 2)之后每次可以取的石子数至少为1,至多为对手刚取的石子数的 ...