Synchronization 同步

来源:互联网 发布:私人网络国债基金产品 编辑:程序博客网 时间:2024/05/16 05:46

Synchronization

同步

Throughout its execution, each thread is isolated from other threads because it has been given its
own method-call stack. However, threads can still interfere with each other when they access and
manipulate shared data. This interference can corrupt the shared data, and this corruption can cause

an application to fail.

在其执行中,每个线程和其它线程隔离,因为它有自己的方法-调用栈。然而,当它们访问和处理共享数据时,线程仍然可能相互干扰。这种干扰可能损坏共享数据,并且损坏能导致一个应用程序失败。

For example, consider a checking account in which a husband and wife have joint access. Suppose
that the husband and wife decide to empty this account at the same time without knowing that the
other is doing the same thing. Listing 7-18 demonstrates this scenario.

考虑丈夫和妻子共同访问一个活期帐户。

清单7-18 有问题的活期帐户

public class CheckingAccount
{
private int balance;
public CheckingAccount(int initialBalance)
{
balance = initialBalance;
}
public boolean withdraw(int amount)
{
if (amount <= balance)     //
{
try
{
Thread.sleep((int) (Math.random() * 200));
}
catch (InterruptedException ie)
{
}
balance -= amount;  //
return true;
}
return false;
}
public static void main(String[] args)
{
final CheckingAccount ca = new CheckingAccount(100);
Runnable r = new Runnable()
{
public void run()
{
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++)
System.out.println (name + " withdraws $10: " +
ca.withdraw(10));
}
};
Thread thdHusband = new Thread(r);
thdHusband.setName("Husband");
Thread thdWife = new Thread(r);
thdWife.setName("Wife");
thdHusband.start();
thdWife.start();
}
}

This application lets more money be withdrawn than is available in the account. For example,
the following output reveals $110 being withdrawn when only $100 is available:
这个应用可以取得超过帐户可用的钱数。

Wife withdraws $10: true
Husband withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Wife withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Wife withdraws $10: true
Wife withdraws $10: false
Wife withdraws $10: false
Wife withdraws $10: false

The reason why more money is withdrawn than is available for withdrawal is that arace condition
exists between the husband and wife threads.

超过可取钱数的更多钱被取的原因是在丈夫与妻子的线程之间存在一个竞争危害。

Note A race condition is a scenario in which multiple threads are accessing shared data and the final result
of these accesses is dependent on the timing of how the threads are scheduled. Race conditions can lead to
bugs that are hard to find and results that are unpredictable.

注意 一个竞争危害是这样一种情形:多个线程正访问共享数据并且这些访问的最终结果依赖于线程被调度的时序。竞争危害可能导致很难发现的错误和不可预知的结果。

A race condition or race hazard is the behavior of an electronic, software or other system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended. The term originates with the idea of two signals racing each other to influence the output first.

Race conditions can occur in electronics systems, especially logic circuits, and in computer software, especially multithreaded or distributed programs.

竞态条件或竞争危害是电子、软件或其他系统的行为,输出是依赖于其他无法控制的事件的顺序或时序设置。事件没有以程序员打算的顺序发生时,它成为一个错误。这个词起源于两个信号彼此竞赛、影响谁最先输出的概念。竞态条件可能发生在电子系统中,特别是逻辑电路、计算机软件,尤其是多线程或分布式程序。

In this example, there is a race condition between checking the amount for withdrawal to ensure that

it is less than what appears in the balance and deducting the amount from the balance. The race
condition exists because these actions are not atomic (indivisible) operations. (Although atoms are
divisible, atomic is commonly used to refer to something being indivisible.)

在这个例子中,在检查要取的金额以确保它少于帐户余额和从余额中扣除这个金额之间存在一个竞争危害。这个竞争危害存在是因为这些动作不是原子(不可分割的)操作。(虽然原子是可分的,但原子被普遍用来指代不可分的某种东西。)

Note The Thread.sleep() method call that sleeps for a variable amount of time (up to a maximum of
199 milliseconds) is present so that you can observe more money being withdrawn than is available for
withdrawal. Without this method call, you might have to execute the application hundreds of times (or more) to
witness this problem, because the scheduler might rarely pause a thread between the amount <= balance
expression and the balance -= amount; expression statement; the code executes rapidly.

注意 出现 Thread.sleep() 方法调用(睡眠可变长度时间,最多199毫秒)是为了你能够观察到取出超过可取金额的钱。

Consider the following scenario:

考虑下述情形:

The Husband thread executes   withdraw()’s amount <= balance expression,
which returns true. The scheduler then suspends the Husband thread and
executes the Wife thread.
The Wife thread executes    withdraw()’s amount <= balance expression, which returns true.
The Wife thread performs the withdrawal. The scheduler suspends the Wife  thread and resumes the Husband thread.
The Husband thread performs the withdrawal. 


This problem can be corrected by synchronizing access to withdraw() so that only one thread
at a time can execute inside this method. You can synchronize access to this method by adding
reserved word synchronized to the method header prior to the method’s return type, for example,
synchronized boolean withdraw(int amount).

这个问题可以通过同步对withdraw()方法的访问来改正,这样,在一个时间只有一个线程可以进入这个方法执行。你可以通过在这个方法的头部添加保留字synchronized到返回类型的前面来同步对这个方法的访问,例如synchronized boolean withdraw(int amount)。

When you run the modified CheckingAccount application, you should observe output similar to the
following:
Husband withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Wife withdraws $10: true
Wife withdraws $10: false
Wife withdraws $10: false
Wife withdraws $10: false
Wife withdraws $10: false
Wife withdraws $10: false

The need for synchronization is often subtle. For example, Listing 7-19’s ID utility class declares
a getNextID() method that returns a unique long-based ID, perhaps to be used when generating
unique filenames. Although you might not think so, this method can cause data corruption and
return duplicate values on a 32-bit machine.

对同步的需求经常是微妙的。这个方法可能产生数据损害并在32位机上返回重复值。


Listing 7-19. A Utility Class for Returning Unique IDs

一个实用类


class ID
{
private static long nextID = 0;
static long getNextID()
{
return nextID++;
}
}

Data corruption occurs because 32-bit virtual machine implementations require two steps to update
a 64-bit long integer and adding 1 to nextID is not atomic: the scheduler could interrupt a thread that
has only updated half of nextID, which corrupts the contents of this variable.

出现数据损坏,是因为32位虚拟机实现需要两步来更新一个64位 long型整数,并且添加1到nextID不是原子的:调度器可能中断一个只更新了nextID一半的线程,这损坏了该变量的内容。


Data corruption refers to errors in computer data that occur during writing, reading, storage, transmission, or processing, which introduce unintended changes to the original data. Computer, transmission and storage systems use a number of measures to provide end-to-end data integrity, or lack of errors.(译者补充)

Note Variables of type long and double are subject to corruption when being written to in an unsynchronized context on 32-bit virtual machines. This problem doesn’t occur with variables of type boolean, byte, char, float, int, or short; each type occupies 32 bits or less.

注意在32位虚拟机上的非同步上下文中时,long 和double类型变量受到损坏。这个问题不会发生在类型为布尔值、字节、字符、浮点数、整数、或短整型类型的变量身上;它们占用32位或者更少。

Duplicate values are returned because postincrement (++) reads and writes the nextID field in two steps, thread A reads nextID but does not increment its value before being interrupted by the  scheduler, and thread B executes and reads the same value.

返回重复值,是因为后递增(++)用两步读和写nextID 字段,线程线程A读nextID 但没有递增它的值,这时被调度器中断,线程B执行并读相同值。

Both problems can be corrected by synchronizing access to nextID so that only one thread can
execute this method’s code. All that is required is to add synchronized to the method header prior to
the method’s return type; for example, static synchronized int getNextID().

两个问题都可以通过同步对nextID的访问(这样只有一个线程能够执行这个方法代码)来改正。

As I will demonstrate later, you can also synchronize access to a block of statements by specifying the following syntax:

同步对语句块的访问。

synchronized(object)
{
/* statements */
}
According to this syntax, object is an arbitrary object reference.

根据这个语法,object是任意一个对象引用。


Mutual Exclusion, Monitors, and Locks

互斥、监视器和锁


Whether you are synchronizing access to a method or a block of statements, no thread can enter
the synchronized region until a thread that’s already executing inside that region leaves it. This
property of synchronization is known as mutual exclusion.

无论你是同步访问方法还是语句块,在已经在区域内部执行的线程离开它之前,没有线程可以进入同步区域内部。同步的这个性质被称为互斥

Mutual exclusion is implemented in terms of monitors and locks. A monitor is a concurrency
construct for controlling access to a critical section, a region of code that must execute atomically.
It is identified at the source code level as a synchronized method or a synchronized block.

互斥是用监视器和锁实现的。一个监视器是控制对临界(必须原子地执行的代码区域)访问的并发构造。它被在源代码级标识成同步方法或同步块。

A lock is a token that a thread must acquire before a monitor allows that thread to execute inside
a monitor’s critical section. The token is released automatically when the thread exits the monitor,
to give another thread an opportunity to acquire the token and enter the monitor.

锁是线程必须获得的令牌,监视器才允许该线程一个监视器的临界键段中执行。当线程退出监视器时令牌自动被释放,给另一个线程获得这个令牌并进入监视器的机会。

Note A thread that has acquired a lock doesn’t release this lock when it calls one of Thread’s sleep() methods.

注意 已经获得锁的线程,当它调用Thread 的sleep()方法之一时不会释放锁

A thread entering a synchronized instance method acquires the lock associated with the object
on which the method is called. A thread entering a synchronized class method acquires the lock
associated with the class’s java.lang.Class object. Finally, a thread entering a synchronized block
acquires the lock associated with the block’s controlling object.

一个正进入同步实例方法的线程获得与对象(调用的是该对象的方法关联的锁。正进入同步类方法的线程获得与该类的 java.lang.Class对象关联的锁。最后,正进入同步块的线程获得与该块的控制对象关联的锁。

Tip  Thread declares a static boolean holdsLock(Object o) method that returns true when the
calling thread holds the monitor lock on object o. You will find this method handy in assertion statements,
such as assert Thread.holdsLock(o);.

提示 


Visibility

可见性

For performance reasons, each thread can have its own copy of a shared variable stored in a local
cache (localized high-speed memory). Without synchronization, one thread’s write to its copy will
not be visible to other thread’s copies. Ideally, when a thread updates a shared variable, this update
should be made to the copy stored in main memory so that other threads can see these updates.

由于性能的原因,每个线程能够有它自己的共享变量副本,存储在本地高速缓存(本地化高速内存)中。如果没有同步,一个线程它的副本,其他线程的副本将看不见。理想情况下,当一个线程更新共享变量,这个更新应该针对存储在主存里的副本,这样其他线程可以看到这些更新。

Synchronization also has the property of visibility in which shared variable values in main memory
are copied to cache memory upon entry to a critical section and copied from cache memory to main
memory upon exit from a critical section. Visibility addresses the vagaries of memory caching and
compiler optimizations that might otherwise prevent one thread from observing another thread’s
update of a shared variable.

同步也有可见性属性在进入临界区时,主存中的共享变量值被复制到缓存,在退出临界段时从缓存复制到主存。可见性应付各种各样的内存缓冲和编译器优化问题,否则可能会阻止一个线程观察另一个线程对共享变量的更新。


Visibility makes it possible to communicate between threads. For example, you might design your
own mechanism for stopping a thread (because you cannot use Thread’s unsafe stop() methods for
this task). Listing 7-20 shows how you might accomplish this task.

可见性使线程之间进行通信成为可能。例如,你可以设计你自己的停止一个线程的机制(因为你不能使用thread 的不安全stop()方法)。清单7-20展示如何完成这个任务。

Listing 7-20. Attempting to Stop a Thread

清单7-20 试着停止一个Thread


public class ThreadStopping
{
public static void main(String[] args)
{
class StoppableThread extends Thread
{
private boolean stopped = false;

@Override
public void run()
{
while(!stopped)  //不停止
System.out.println("running");
}

void stopThread()   //停止方法
{
stopped = true;
}


}
StoppableThread thd = new StoppableThread();
thd.start();
try
{
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie)
{
}
thd.stopThread();
}
}

Listing 7-20 introduces a main() method with a local class named StoppableThread that subclasses
Thread. StoppableThread declares a stopped field initialized to false, a stopThread() method that
sets this field to true, and a run() method whose infinite loop checks stopped on each loop iteration
to see if its value has changed to true.


After instantiating StoppableThread, the default main thread starts the thread associated with this
Thread object. It then sleeps for one second and calls StoppableThread’s stop() method before
dying. When you run this application on a single-processor/single-core machine, you will probably
observe the application stopping. You might not see this stoppage when the application runs on a
multiprocessor machine or a uniprocessor machine with multiple cores where each processor or
core probably has its own cache with its own copy of stopped. When one thread modifies its copy of
this field, the other thread’s copy of stopped isn’t changed.

当这个应用程序运行在多处理器或多核机器(每个处理器或核可能使自己的高速缓存具有自己的stopped副本)上时你可能没看见停止。当一个线程修改它的这个字段的副本时,其它线程的 stopped副本没有变化。


Listing 7-21 refactors Listing 7-20 to guarantee that the application will run correctly on all kinds of
machines.


Listing 7-21. Guaranteed Stoppage on a Multiprocessor/Multicore Machine

清单7-21 保证在多处理器/多核机器上停止


public class ThreadStopping
{
public static void main(String[] args)
{
class StoppableThread extends Thread
{
private boolean stopped = false;

@Override
public void run()
{
while(!isStopped())
System.out.println("running");
}


synchronized void stopThread()
{
stopped = true;
}


private synchronized boolean isStopped()
{
return stopped;
}


}
StoppableThread thd = new StoppableThread();
thd.start();
try
{
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie)
{
}
thd.stopThread();
}
}

Listing 7-21’s stopThread() and isStopped() methods are synchronized to support visibility so that
the default main thread that calls stopThread() and the started thread that executes inside run() can
communicate. When a thread enters one of these methods, it’s guaranteed to access a single shared
copy of the stopped field (not a cached copy).

清单7-21的stopThread()和isStopped()方法被同步以支持可见性,这样调用stopThread()的默认主线程和执行在run内部的启动线程之间可以沟通。当一个线程进入这些方法之一,保证访问stopped字段的单个共享副本(不是一个高速缓冲副本)。

Synchronization gives us mutual exclusion and visibility. Because you don’t need mutual exclusion
in this example (there is no race condition), you can refactor Listing 7-21 to take advantage of Java’s
volatile reserved word, which supports visibility only, and which Listing 7-22 demonstrates.

Synchronization给我们互斥和可见性。因为在这个例子中你不需要互斥(没有竞争情况),你可以重构清单7-21以利用Java 的volatile 保留字,它只支持可见性,清单7-22表明了这一点。

Listing 7-22. The volatile Alternative to Synchronization

清单7-22 同步的volatile替代
public class ThreadStopping
{
public static void main(String[] args)
{
class StoppableThread extends Thread
{
private volatile boolean stopped = false;
@Override
public void run()
{
while(!stopped)
System.out.println("running");
}
void stopThread()
{
stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try
{
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie)
{
}
thd.stopThread();
}
}

Listing 7-22 declares stopped to be volatile. Threads that access this field will always access
a single shared copy (not cached copies on multiprocessor/multicore machines).

清单7-22 声明stopped为volatile。访问这个字段的线程们将总是访问单一共享副本(在多处理器/ 多核机器上非高速缓冲版本)。

When a field is declared volatile, it cannot also be declared final. If you’re depending on the
semantics (meaning) of volatility, you still get those from a final field. For more information, check
out Brian Goetz’s “Java theory and practice: Fixing the Java Memory Model, Part 2” article
(www.ibm.com/developerworks/library/j-jtp03304/).

Caution Use volatile only in a thread communication context. Also, you can only use this reserved word
in the context of field declarations. Although you can declare double and long fields volatile, you should
avoid doing so on 32-bit virtual machines because it takes two operations to access a double or long
variable’s value, and mutual exclusion (via synchronization) is required to access their values safely.

警告 只在线程通信的上下文中使用 voilatile。而且你只能在字段声明的上下文中使用这个保留字。虽然你可以声明duble和long字段为volatile ,你应该避免在32位虚拟机上这样做,因为它访问一个double或long变量的值要两次完成,并且安全访问它们的值需要互斥(通过synchronized)。


Waiting and Notification

等待和通知


The java.lang.Object class provides wait(), notify(), and notifyAll() methods to support a
form of thread communication where a thread voluntarily waits for some condition (a prerequisite
for continued execution) to arise, at which time another thread notifies the waiting thread that it
can continue. wait() causes its calling thread to wait on an object’s monitor, and notify() and
notifyAll() wake up one or all threads waiting on the monitor.

 java.lang.Object 类提供wait()、notify()和notifyAll()方法以支持一种形式的线程通信,即:一个线程自动等待某个条件(一个执行的先决条件)出现,到时另一个线程通知那个等待的线程它可以继续。wait()导致它的调用线程等待对象监视器,并且notify()和notifyAll()唤醒一个或所有等待这个监视器的线程。

Caution Because the wait(), notify(), and notifyAll() methods depend on a lock, they cannot be
called from outside of a synchronized method or synchronized block. If you fail to heed this warning, you will
encounter a thrown instance of the java.lang.IllegalMonitorStateException class. Also, a thread
that has acquired a lock releases this lock when it calls one of Object’s wait() methods.

警告 而且,已经获得锁的线程,当它调用Object的wait()方法之一时,释放这把锁。

A classic example of thread communication involving conditions is the relationship between a
producer thread and a consumer thread. The producer thread produces data items to be consumed
by the consumer thread. Each produced data item is stored in a shared variable.

制造的每个数据项被存储在一个共享变量中。
Imagine that the threads are running at different speeds. The producer might produce a new data
item and record it in the shared variable before the consumer retrieves the previous data item for
processing. Also, the consumer might retrieve the contents of the shared variable before a new data
item is produced.


To overcome those problems, the producer thread must wait until it is notified that the previously
produced data item has been consumed, and the consumer thread must wait until it is notified that
a new data item has been produced. Listing 7-23 shows you how to accomplish this task via wait()
and notify().


Listing 7-23. The Producer-Consumer Relationship, Version 1

清单7-23 生产者-消费者联系,版本1

public class PC
{
public static void main(String[] args)
{
Shared s = new Shared();
new Producer(s).start();
new Consumer(s).start();
}
}


class Shared
{
private char c = '\u0000';

/*

\u开头的是一个Unicode码的字符,每一个'\u0000'都代表了一个空格

*/
private boolean writeable = true;
synchronized void setSharedChar(char c)    //往里写一个字符
{
while (!writeable)  //不可写
try
{
wait();
}
catch (InterruptedException e) {}
this.c = c;

writeable = false; //不可写
notify();  //通知可以消费了
}


synchronized char getSharedChar()  //取字符
{
while (writeable)     //可写不可读
try
{
wait();
}
catch (InterruptedException e) {}
writeable = true;
notify();  //通知可以写了
return c;
}
}


class Producer extends Thread
{
private Shared s;
Producer(Shared s)
{
this.s = s;
}


@Override
public void run()
{
for (char ch = 'A'; ch <= 'Z'; ch++)
{
s.setSharedChar(ch);
System.out.println(ch + " produced by producer.");
}
}
}


class Consumer extends Thread
{
private Shared s;
Consumer(Shared s)
{
this.s = s;
}
@Override
public void run()
{
char ch;
do

{
ch = s.getSharedChar();
System.out.println(ch + " consumed by consumer.");
}
while (ch != 'Z');
}
}


This application creates a Shared object and two threads that get a copy of the object’s reference.
The producer calls the object’s setSharedChar() method to save each of 26 uppercase letters; the
consumer calls the object’s getSharedChar() method to acquire each letter.

这个应用程序建立一个Shared对象和得到这个对象引用副本的两个线程。


The writeable instance field tracks two conditions: the producer waiting on the consumer to
consume a data item and the consumer waiting on the producer to produce a new data item. It helps
coordinate execution of the producer and consumer. The following scenario, where the consumer
executes first, illustrates this coordination:

下列消费者先执行的情形说明这个协调:

1.  The consumer executes s.getSharedChar() to retrieve a letter.
2.  Inside of that synchronized method, the consumer calls wait() because
writeable contains true. The consumer now waits until it receives notification
from the producer.
3.  The producer eventually executes s.setSharedChar(ch);.

3. 生产者终于执行s.setSharedChar(ch);。
4.  When the producer enters that synchronized method (which is possible
because the consumer released the lock inside of the wait() method prior to
waiting), the producer discovers writeable’s value to be true and doesn’t call
wait().

4. 当生产者进入这个同步方法时(那是可能的,因为消费者在等待之前在wait()方法内释放锁),生产者发现writeable的值是ture,就不调用wait()。

5.  The producer saves the character, sets writeable to false (which will cause
the producer to wait on the next setSharedChar() call when the consumer
has not consumed the character by that time), and calls notify() to awaken
the consumer (assuming the consumer is waiting).
6.  The producer exits setSharedChar(char c).
7.  The consumer wakes up (and reacquires the lock), sets writeable to true
(which will cause the consumer to wait on the next getSharedChar() call
when the producer has not produced a character by that time), notifies the
producer to awaken that thread (assuming the producer is waiting), and
returns the shared character.

Although the synchronization works correctly, you might observe output (on some platforms) that
shows multiple producing messages before multiple consuming messages. For example, you might
see A produced by producer., followed by B produced by producer., followed by A consumed
by consumer., at the beginning of the application’s output.
This strange output order is caused by the call to setSharedChar() followed by its companion
System.out.println() method call not being atomic, and by the call to getSharedChar() followed
by its companion System.out.println() method call not being atomic. The output order can be

corrected by wrapping each of these method call pairs in a synchronized block that synchronizes on
the Shared object referenced by s. Listing 7-24 presents this enhancement.

可以通过用一个同步(同步在由s引用的Shared 对象上)块中包装这些方法调用对来改正输出顺序。

Listing 7-24. The Producer-Consumer Relationship, Version 2

版本2

public class PC
{
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;
notify();
}
synchronized char getSharedChar()
{
while (writeable)
try
{
wait();
}
catch (InterruptedException e) {}
writeable = true;
notify();
return c;
}
}
class Producer extends Thread
{
private Shared s;

Producer(Shared s)
{
this.s = s;
}
@Override
public void run()
{
for (char ch = 'A'; ch <= 'Z'; ch++)
{
synchronized(s)
{
s.setSharedChar(ch);
System.out.println(ch + " produced by producer.");
}
}
}
}
class Consumer extends Thread
{
private Shared s;
Consumer(Shared s)
{
this.s = s;
}


@Override
public void run()
{
char ch;
do
{
synchronized(s)
{
ch = s.getSharedChar();
System.out.println(ch + " consumed by consumer.");
}
}
while (ch != 'Z');
}
}

Compile Listing 7-24 (javac PC.java) and run this application (java PC). Its output should always
appear in the same alternating order as shown next (only the first few lines are shown for brevity):
A produced by producer.
A consumed by consumer.
B produced by producer.
B consumed by consumer.
C produced by producer.

C consumed by consumer.
D produced by producer.
D consumed by consumer.

Caution Never call wait() outside of a loop.The loop tests the condition (!writeable or writeable
in the previous example) before and after the wait() call. Testing the condition before calling wait()
ensures liveness. If this test was not present, and if the condition held and notify() had been called prior to
wait() being called, it is unlikely that the waiting thread would ever wake up. Retesting the condition after
calling wait() ensures safety. If retesting didn’t occur, and if the condition didn’t hold after the thread had
awakened from the wait() call (perhaps another thread called notify() accidentally when the condition
didn’t hold), the thread would proceed to destroy the lock’s protected invariants.

注意 永远不要在循环的外面调用wait()。






0 0