16.6.2 使用Condition控制线程通信

16.6.2 使用Condition控制线程通信

synchronized同步的 使用同步监视器进行通信

如果程序不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用wait()notify()notifyAll()方法进行线程通信了。

Lock对象同步的使用Condition对象通信

当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程

Condition将同步监视器方法(wait()notify()notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set)。
在这种情况下,

  • Lock替代了同步方法或同步代码块,
  • Condition替代了同步监视器的功能

如何创建Condition实例

Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition方法即可

Condition类提供了如下三个方法。

方法 描述
void await() 类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Conditionsignal()方法或signalAll()方法来唤醒该线程
void signal() 唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择是仼意性的。只有当前线程放弃对该Lock对象的锁定后(使用await方法),才可以执行被唤醒的线程。
void signalAll() 唤醒在此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后才可以执行被唤醒的线程。
await方法变体 描述
boolean await(long time, TimeUnit unit) Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.
long awaitNanos(long nanosTimeout) Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.
void awaitUninterruptibly() Causes the current thread to wait until it is signalled.
boolean awaitUntil(Date deadline) Causes the current thread to wait until it is signalled or interrupted, or the specified deadline elapses.

程序 使用Lock同步 使用Condition通信

账户类

下面程序中Account使用Lock对象来控制同步,并使用Condition对象来控制线程的协调运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {
// 显式定义Lock对象
private final Lock lock = new ReentrantLock();
// 获得指定Lock对象对应的Condition
private final Condition cond = lock.newCondition();
// 封装账户编号、账户余额的两个成员变量
private String accountNo;
private double balance;
// 标识账户中是否已有存款的旗标
private boolean flag = false;

public Account() {
}

// 构造器
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}

// accountNo的setter和getter方法
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}

public String getAccountNo() {
return this.accountNo;
}

// 因此账户余额不允许随便修改,所以只为balance提供getter方法,
public double getBalance() {
return this.balance;
}
// 取钱
public void draw(double drawAmount) {
// 加锁
lock.lock();
try {
// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
if (!flag) {
cond.await();
} else {
// 执行取钱
System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:" + balance);
// 将标识账户是否已有存款的旗标设为false。
flag = false;
// 唤醒其他线程
cond.signalAll();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// 使用finally块来释放锁
finally {
lock.unlock();
}
}
// 存钱
public void deposit(double depositAmount) {
lock.lock();
try {
// 如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
if (flag) // ①
{
cond.await();
} else {
// 执行存款
System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount);
balance += depositAmount;
System.out.println("账户余额为:" + balance);
// 将表示账户是否已有存款的旗标设为true
flag = true;
// 唤醒其他线程
cond.signalAll();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// 使用finally块来释放锁
finally {
lock.unlock();
}
}

// 下面两个方法根据accountNo来重写hashCode()和equals()方法
public int hashCode() {
return accountNo.hashCode();
}

public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj != null && obj.getClass() == Account.class) {
Account target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}

用该程序与上一节的Account.java进行对比,不难发现这两个程序的逻辑基本相似,只是现在显式地使用Lock对象来充当同步监视器,然后需要使用Condition对象来暂停、唤醒指定线程
该示例程序的其他类与前一个示例程序的其他类完全一样,该程序的运行效果与前一个示例程序的运行效果完全一样.

使用管道流进行线程通信

本书第1版还介绍了一种使用管道流进行线程通信的情形,但实际上由于两个线程属于同一个进程,它们可以非常方便地共享数据,因此很少需要使用管道流进行通信,故此处不再介绍那种烦琐的方式。