提交 21fb8982 编写于 作者: sinat_29705095's avatar sinat_29705095

暂存

上级 1d519e3d
# Chapter 10 Concurrency andSynchronizati on
> 译者:[Ruffianjiang](https://github.com/Ruffianjiang)
An implicit assumption in everything we’ve done so far is that a single program is modifying our data structures. In Java, one can have the effect of multiple programs modifying an object, due to the existence of threads
​ Although the language used to describe threads suggests that their purpose is to allow several things to happen simultaneously, this is a somewhat misleading impression. Even the smallest Java application running on Sun ’s JDK platform, for example, has five threads, and that’s only if the application has not created any itself, and even if the machine on which the program runs consists of a single processor (which can only execute one instruction at a time). The four additional “system threads” perform a number of tasks (such as “finalizing” objects that are no longer reachable by the program) that are logically independent of the rest of the program.Their actions could usefully occur at any time relative to the rest of the program.Sun’s Java run time system, in other words, is using threads as a organizational tool for its system.
​ Threads abound in Java programs that use graphical user interfaces(GUIs). One thread draws or redraws the screen. Another responds to e vents such as the clicking of a mouse button at some point on the screen. These are related,but largely independent activities: objects must be redrawn, for example, whenever a window becomes invisible and uncovers them, which happens independently of any calculations the program is doing.Threads violate our implicit assumption that a single program operates on our data, so that even an otherwise perfectly implemented data structure, with all of its instance variables private, can become corrupted in rather bizarre ways. The existence of multiple threads operating on the same data objects also raises the general problem of how these threads are to communicate with each other in an orderly fashion.
## 10.1 Synchronized Data Structures
Consider the `ArrayList` implementation from §4.1. In the method `ensureCapacity`, we find
```java
public void ensureCapacity(int N) {
if (N <= data.length)
return;
Object[] newData = new Object[N];
System.arraycopy (data, 0,newData, 0, count);
data = newData;
}
```
```java
public Object set(int k, Object x) {
check (k, count);
Object old = data[k];
data[k] = x;
return old;
}
```
Suppose one program executes `ensureCapacity` while another is executing set on the same `ArrayList` object. We could see the following interleaving of their actions:
```java
/* Program 1 executes: */ newData = new Object[N];
/* Program 1 executes: */ System.arraycopy (data, 0,newData, 0, count);
/* Program 2 executes: */ data[k] = x;
/* Program 1 executes: */ data = newData;
```
Thus, we lose the value that Program 2 set, because it puts this value into the old value of `data` after `data’s` contents have been copied to the new, expanded array. To solve th e simple problem presented by `ArrayList`, threads can arrange to access any particular `ArrayList` in mutual exclusion—that is, in such a way that only one thread at a time operates on the object. Java’s `synchronized` statement provide mutual exclusion, allowing us to produce `synchronized` (or thread-safe) data structures. Here is part of an example, showing both the use of the `synchronized` method modifier and equivalent use of the synchronized statement:
```java
public class SyncArrayList<T> extends ArrayList<T> {
...
public void ensureCapacity(int n) {
synchronized (this) {
super.ensureCapacity (n);
}
}
public synchronized T set(int k, T x) {
return super.set (k, x);
}
...
}
```
​ The process of providing such wrapper functions for all methods of a List is sufficiently tedious that the standard Java library class `java.util.Collections` provides the following method:
```java
/** A synchronized (thread-safe) view of the list L, in which only
* one thread at a time executes any method. To be effective,
* (a) there should be no subsequent direct use of L,
* and (b) the returned List must be synchronized upon
* during any iteration, as in
*
* List aList = Collections.synchronizedList(new ArrayList());
* ...
* synchronized(aList) {
* for (Iterator i = aList.iterator(); i.hasNext(); )
* foo(i.next());
* }
*/
public static List<T> synchronizedList (List L<T>) { ... }
```
Unfortunately, there is a time cost associated with synchronizing on every operation, which is why the Java library designers decided that `Collection` and most of its subtypes would not be synchronized. On the other hand, `StringBuffers` and `Vectors` are synchronized, and cannot be corrupted by simultaneous use.
## 10.2 Monitors and Orderly Communication
The objects returned by the `synchronizedList` method are examples of the simplest kind of monitor. This term refers to an object (or type of object) that controls (“monitors”) concurrent access to some data structure so as to make it work correctly. One function of a monitor is to provide mutually exclusive access to the operations of the data structure, where needed. Another is to arrange for synchronization between threads—so that one thread can wait until an object is “ready” to provide it with some service.
​ Monitors are exemplified by one of the classic examples: the shared buffer or mailbox. A simple version of its public specification looks like this:
```java
/** A container for a single message (an arbitrary Object). At any
* time, a SmallMailbox is either empty (containing no message) or
* full (containing one message). */
public class SmallMailbox {
/** When THIS is empty, set its current message to MESSAGE, making
* it full. */
public synchronized void deposit (Object message)
throws InterruptedException { ... }
/** When THIS is full, empty it and return its current message. */
public synchronized Object receive ()
throws InterruptedException { ... }
}
```
Since the specifications suggest that either method might have to wait for a new message to be deposited or an old one to be received, we specify both as possibly throwing an `InterruptedException`, which is the standard Java way to indicate that while we were waiting, some other thread interrupted us.
The `SmallMailbox` specification illustrates the features of a typical monitor:
- None of the modifiable state variables (i.e., fields) are exposed.
- Accesses from separate threads that make any reference to modifiable state are mutually excluded; only one thread at a time holds a lock on a `SmallMailbox` object.
- A thread may relinquish a lock temporarily and await notification of some change. But changes in the ownership of a lock occur only at well-defined points in the program.
The internal representation is simple:
```java
private Object message;
private boolean amFull;
```
The implementations make use of the primitive Java features for “waiting until notified:”
```java
public synchronized void deposit (Object message)
throws InterruptedException{
while (amFull)
wait (); // Same as this.wait ();
this.message = message;
this.amFull = true;
notifyAll (); // Same as this.notifyAll ()
}
public synchronized Object receive ()
throws InterruptedException{
while (! amFull)
wait ();
amFull = false;
notifyAll ();
return message;
}
```
The methods of `SmallMailbox` allow other threads in only at carefully controlled points: the calls to wait. For example, the loop in `deposit` means “If there is still old unreceived mail, wait until some other thread to receives it and wakes me up again (with `notifyAll`) and I have managed to lock this mailbox again.” From the point of view of a thread that is executing `deposit` or `receive`, each call to `wait` has th e effect of causing some change to the in stance variables of `this`—some change, that is, that could be effected by other calls `deposit` or `receive`.
​ As long as the threads of a program are careful to protect all their data in monitors in this fashion, they will avoid the sorts of bizarre interaction described at the beginning of §10.1. Of course, there is no such thing as a free lunch; the use of locking can lead to the situation known as deadlock in which two or more threads wait for each other indefinitely, as in this artificial example:
```java
class Communicate {
static SimpleMailbox
box1 = new SimpleMailbox (),
box2 = new SimpleMailbox ();
}
// Thread #1: | // Thread #2:
m1 = Communicate.box1.receive ();| m2 = Communicate.box2.receive();
Communicate.box2.deposit (msg1); | Communicate.box1.deposit (msg2);
```
Since neither thread sends anything before trying to receive a message from its box, both threads wait for each other (the problem could be solved by having one of the two threads reverse the order in which it receives and deposits).
## 10.3 Message Passing
Monitors provide a disciplined way for multiple threads to access data without stumbling over each other. Lurking behind the concept of monitor is a simple idea:
> Thinking about multiple programs executing simultaneously is hard, so don’t do it! Instead, write a bunch of one-thread programs, and have them exchange data with each other
In the case of general monitors, “exchanging data” means setting variables that each can see. If we take the idea further, we can in stead define “exchanging data” as “reading input and writing output.” We get a concurrent programming discipline called message passing.
​ In the message-passing world, threads are independent sequential programs than send each other messages. They read and write messages using methods that correspond to `read` on Java `Readers`, or `print` on Java `PrintStreams`. As a result, one thread is affected by another only when it bothers to “read its messages.”
​ We can get the effect of message passing by writing our threads to perform all interaction with each other by means of mailboxes. That is, the threads share some set of mailboxes, but share no other modifiable objects or variables (unmodifiable objects, like Strings, are fine to share)
## Exercises
**10.1** Give a possible implementation for the `Collections.synchronizedList` static method in §10.1.
# Chapter 10 Concurrency andSynchronizati on
# 第10章 并发和同步
> 译者:[Ruffianjiang](https://github.com/Ruffianjiang)
An implicit assumption in everyth ing we’ve done so far is that a single program ismodifying our data structures. In Java, one can have the effect of multiple programsmodifying an object, due to the existence of threads
到目前为止,我们所做的每件事都隐含着一个假设,即一个程序正在修改我们的数据结构。在Java中,由于线程的存在,可能会产生多个程序使对象的效果
​ Although the language used to describe threads suggests that their purpose is to allow several things to happen simultaneously, this is a somewhat misleading impression. Even the smallest Java application running on Sun ’s JDK platform, for example, has five threads, and that’s only if the application has not created any itself, and even if the machine on which the program runs consists of a single processor (which can only execute one instruction at a time). The four additional “system threads” perform a number of tasks (such as “finalizing” objects that are no longer reachable by the program) that are logically independent of the rest of the program.Their actions could usefully occur at any time relative to the rest of the program.Sun’s Java run time system, in other words, is using threads as a organizational tool for its system.
尽管用于描述线程的语言表明,线程的目的是允许同时发生几件事情,但这在一定程度上是一种误导。即使是最小的Java应用程序运行在太阳的JDK的平台,例如,有五个线程,这是只有在应用程序没有创建任何本身,即使程序运行的机器由一个单处理器(一次只能执行一个指令)。这四个额外的“系统线程”执行许多任务(例如“终结”程序不再可访问的对象),这些任务在逻辑上独立于程序的其余部分。相对于程序的其余部分,它们的操作可以在任何时候有效地发生。换句话说,Sun的Java运行时系统使用线程作为其系统的组织工具。
​ Threads abound in Java programs that use graphical user interfaces(GUIs). One thread draws or redraws the screen. Another responds to e vents such as the clicking of a mouse button at some point on the screen. These are related,but largely independent activities: objects must be redrawn, for example, whenever a window becomes invisible and uncovers them, which happens independently of any calculations the program is doing.Threads violate our implicit assumption that a single program operates on our data, so that even an otherwise perfectly implemented data structure, with all of its instance variables private, can become corrupted in rather bizarre ways. The existence of multiple threads operating on the same data objects also raises the general problem of how these threads are to communicate with each other in an orderly fashion.
## 10.1 Synchronized Data Structures
使用图形用户界面(gui)的Java程序中有大量线程。一个线程绘制或重绘屏幕。另一个响应e通风口,如点击鼠标按钮在屏幕上的某个点。这些都是相关的,但在很大程度上是独立的活动:例如,当一个窗口变得不可见并显示它们时,必须重新绘制对象,这与程序正在执行的任何计算无关。线程违反了我们的隐含假设,即一个程序对我们的数据进行操作,因此,即使是一个完全实现的数据结构,其所有实例变量都是私有的,也可能以相当奇怪的方式受到破坏。在相同数据对象上运行的多个线程的存在也提出了一个普遍的问题,即这些线程如何以有序的方式彼此通信。
## 10.1 同步的数据结构
Consider the `ArrayList` implementation from §4.1. In the method `ensureCapacity`, we find
参考4.1节 `ArrayList` 的实现。在方法 `ensureCapacity` 中,我们发现
```java
public void ensureCapacity(int N) {
if (N <= data.length)
......@@ -31,8 +36,7 @@ public Object set(int k, Object x) {
}
```
Suppose one program executes `ensureCapacity` while another is executing set on the same `ArrayList` object. We could see the following interleaving of their actions:
假设一个程序执行 `ensureCapacity` ,而另一个程序正在相同的 `ArrayList` 对象上执行改动。我们可以看到他们的动作交叉在了一起,如下所示:
```java
/* Program 1 executes: */ newData = new Object[N];
/* Program 1 executes: */ System.arraycopy (data, 0,newData, 0, count);
......@@ -40,10 +44,9 @@ Suppose one program executes `ensureCapacity` while another is executing set on
/* Program 1 executes: */ data = newData;
```
Thus, we lose the value that Program 2 set, because it puts this value into the old value of `data` after `data’s` contents have been copied to the new expanded array. To solve the simple problem presented by `ArrayList`, threads can arrange to access any particular `ArrayList` in mutual exclusion—that is, in such a way that only one thread at a time operates on the object. Java’s `synchronized` statement provide mutual exclusion, allowing us to produce `synchronized` (or thread-safe) data structures. Here is part of an example, showing both the use of the `synchronized` method modifier and equivalent use of the synchronized statement:
Thus, we lose the value that Program 2 set, because it puts this value into th e old value of `data` after `data’s` contents h ave been copied to the new, expanded array. To solve th e simple problem presented by `ArrayList`, threads can arrange to access any particular `ArrayList` in mutual exclusion—that is, in such a way that only one thread at a time operates on the object. Java’s `synchronized` statement provide mutual exclusion, allowing us to produce `synchronized` (or thread-safe) data structures. Here is part of an example, showing both the use of the `synchronized` method modifier and equivalent use of the synchronized statement:
因此,我们丢失了程序2设置的值,因为它在 `data` 的内容复制到新的扩展数组之后,将这个值放入 `data` 的旧值中。为了解决 `ArrayList` 呈现出的简单问题,线程可以以互斥的方式安排访问任何特定的 `ArrayList` —— 也就是说,每次只有一个线程操作对象。Java的“synchronized”语句提供互斥,允许我们生成“synchronized”(或线程安全)数据结构。下面是一个例子的一部分,展示了同步方法修饰符的使用和同步语句的等效使用:
```java
public class SyncArrayList<T> extends ArrayList<T> {
...
......@@ -59,8 +62,7 @@ public class SyncArrayList<T> extends ArrayList<T> {
}
```
​ The process of providing such wrapper functions for all methods of a List is sufficiently tedious that the standard Java library class `java.util.Collections` provides the following method:
为所有方法提供这样的包装函数清单的过程非常繁琐,以至于标准Java库类的 `java.util.Collections` 提供了以下方法:
```java
/** A synchronized (thread-safe) view of the list L, in which only
* one thread at a time executes any method. To be effective,
......@@ -78,14 +80,18 @@ public class SyncArrayList<T> extends ArrayList<T> {
public static List<T> synchronizedList (List L<T>) { ... }
```
Unfortunately, there is a time cost associated with synchronizing on every operation, which is why the Java library designers decided that `Collection` and most of its subtypes would not be synchronized. On the other hand, `StringBuffers` and `Vectors` are synchronized, and cannot be corrupted by simultaneous use.
不幸的是,每个操作的同步都有时间开销,这就是为什么Java库设计人员决定不同步 `Collection` 及其大多数子类型。另一方面,`StringBuffers``Vectors` 是同步的,不能被同时使用而损坏。
## 10.2 Monitors and Orderly Communication
## 10.2 监控与有序通信
The objects returned by the `synchronizedList` method are examples of the simplest kind of monitor. This term refers to an object (or type of object) that controls (“monitors”) concurrent access to some data structure so as to make it work correctly. One function of a monitor is to provide mutually exclusive access to the operations of the data structure, where needed. Another is to arrange for synchronization between threads—so that one thread can wait until an object is “ready” to provide it with some service.
“synchronizedList”方法返回的对象是最简单的监视器的例子。这个术语指的是控制(“监视器”)对某些数据结构的并发访问以使其正确工作的对象(或对象类型)。监视器的一个功能是在需要时提供对数据结构操作的互斥访问。另一种方法是安排线程之间的同步——这样一个线程就可以等待对象“准备好”为它提供一些服务。
​ Monitors are exemplified by one of the classic examples: the shared buffer or mailbox. A simple version of its public specification looks like this:
监视器可以用一个典型的例子来说明:共享缓冲区或邮箱。其公共规范的一个简单版本如下:
```java
/** A container for a single message (an arbitrary Object). At any
* time, a SmallMailbox is either empty (containing no message) or
......@@ -101,24 +107,21 @@ public class SmallMailbox {
}
```
Since the specifications suggest that either method might have to wait for a new message to be deposited or an old one to be received, we specify both as possibly throwing an `InterruptedException`, which is the standard Java way to indicate that while we were waiting, some other thread interrupted us.
自规范表明这两种方法都可能需要等待新的消息沉积或接收旧,我们尽可能地将两者都指定为抛出 `InterruptedException` ,这是标准Java方式表明,我们在等待时,其他线程打断我们。
The `SmallMailbox` specification illustrates the features of a typical monitor:
“SmallMailbox”规范说明了典型监视器的特性:
- 没有任何可修改的状态变量(即,字段)是公开的。
- 从单独的线程访问,使任何对可修改状态的引用相互排除;每次只有一个线程持有 `SmallMailbox` 对象上的锁。
- 线程可以暂时放弃锁,等待某些更改的通知。但是锁的所有权的变化只发生在程序中定义良好的点上。
- None of the modifiable state variables (i.e., fields) are exposed.
- Accesses from separate th reads that make any reference to modifiable state are mutually excluded; only one thread at a time holds a lock on a `SmallMailbox` object.
- A thread may relinquish a lock temporarily and await notification of some change. But changes in the ownership of a lock occur only at well-defined points in the program.
The internal representation is simple:
内部表示很简单:
```java
private Object message;
private boolean amFull;
```
The implementations make use of the primitive Java features for “waiting until notified:”
这些实现使用了基本的Java特性来“等待直到被通知”:
```java
public synchronized void deposit (Object message)
......@@ -141,8 +144,14 @@ public synchronized Object receive ()
The methods of `SmallMailbox` allow other threads in only at carefully controlled points: the calls to wait. For example, the loop in `deposit` means “If there is still old unreceived mail, wait until some other thread to receives it and wakes me up again (with `notifyAll`) and I have managed to lock this mailbox again.” From the point of view of a thread that is executing `deposit` or `receive`, each call to `wait` has th e effect of causing some change to the in stance variables of `this`—some change, that is, that could be effected by other calls `deposit` or `receive`.
“SmallMailbox”的方法只允许其他线程进入精心控制的点:等待的调用。例如,“deposit”中的循环表示“如果仍然有旧的未接收邮件,请等到其他线程接收到它并再次唤醒我(使用‘notifyAll’),然后我再次锁定了这个邮箱”。“从执行‘deposit’或‘receive’的线程的角度来看,每次调用‘wait’都会对‘this’的in stance变量造成一些变化——一些变化可能会受到其他调用‘deposit’或‘receive’的影响。
​ As long as the threads of a program are careful to protect all their data in monitors in this fashion, they will avoid the sorts of bizarre interaction described at the beginning of §10.1. Of course, there is no such thing as a free lunch; the use of locking can lead to the situation known as deadlock in which two or more threads wait for each other indefinitely, as in this artificial example:
只要程序的线程小心翼翼地以这种方式保护它们在监视器中的所有数据,它们就会避免§10.1开头描述的那种奇怪的交互。当然,天下没有免费的午餐;使用锁会导致死锁的情况,即两个或多个线程无限期地等待对方,如下面的人为例子所示:
```java
class Communicate {
static SimpleMailbox
......@@ -154,20 +163,22 @@ m1 = Communicate.box1.receive ();| m2 = Communicate.box2.receive();
Communicate.box2.deposit (msg1); | Communicate.box1.deposit (msg2);
```
Since neither thread sends anything before trying to receive a message from its box, both threads wait for each other (the problem could be solved by having one of the two threads reverse the order in which it receives and deposits).
由于两个线程在试图从其消息框中接收消息之前都不发送任何消息,所以两个线程都彼此等待(可以通过让其中一个线程反转接收和存储消息的顺序来解决这个问题)。
## 10.3 Message Passing
## 10.3 消息传递
Monitors provide a disciplined way for multiple threads to access data without stumbling over each other. Lurking behind the concept of monitor is a simple idea:
监视器(Monitors)为多个线程提供了一种有序的方法来访问数据,而不会互相阻碍。隐藏在监视器概念背后的是一个简单的想法:
> Thinking about multiple programs executing simultaneously is hard, so don’t do it! Instead, write a bunch of one-thread programs, and have them exchange data with each other
> 考虑到同时执行多个程序是很困难的,所以不要这样做!相反,编写一组单线程程序,让它们彼此交换数据
In the case of general monitors, “exchanging data” means setting variables that each can see. If we take the idea further, we can in stead define “exchanging data” as “reading input and writing output.” We get a concurrent programming discipline called message passing.
​ In the message-passing world, threads are independent sequential programs than send each other messages. They read and write messages using methods that correspond to `read` on Java `Readers`, or `print` on Java `PrintStreams`. As a result, one th read is affected by another only when it bothers to “read its messages.”
对于一般监视器,“交换数据”意味着设置每个监视器都可以看到的变量。如果我们更进一步,我们可以将“交换数据”定义为“读取输入和写入输出”。“我们有一个并发的编程规则,叫做消息传递。
​在消息传递的世界中,线程是独立的顺序程序,而不是互相发送消息。他们读写消息使用是在 Java 的 `reader` 上读或在 Java 的 `PrintStreams` 上打印一致的方法。因此,只有当一个线程费力的“读取其消息”时,才会受到另一个线程的影响。
We can get th e effect of message passing by writing our threads to perform all interaction with each other by means of mailboxes. That is, the threads share some set of mailboxes, but share no other modifiable objects or variables (unmodifiable objects, like Strings, are fine to share)
我们可以通过编写线程来实现消息传递的效果,通过邮箱来实现所有的交互。也就是说,线程共享一组邮箱,但是不共享其他可修改的对象或变量(不可修改的对象,比如`Strings`,可以共享)
## Exercises
## 练习
**10.1** Give a possible implementation for the `Collections.synchronizedList` static method in §10.1.
**10.1** 给10.1节的 `Collections.synchronizedList` 提供一个可能的静态方法的实现。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册