本文引用的例子:当线程在系统内运行时,线程的调度具有一定透明性,程序通常无法准确控制线程轮换执行,但是我们可以通过一些机制来保证线程协调运行。为了能说明这个问题,专门引入一个题目:假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者——现在假设系统要求存款者和取钱者不断地重复存款、取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱,不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。
0.知识体系
1.传统线程通信
class Account
{
//定义账户、账户余额
private String accountNo;
private double balance;
//标识账户中是否已有存款
private boolean flag = false;
//构造器
public Account(String accountNo, double balance){
this.accountNo = accountNo;
this.balance = balance;
}
//accountNo的set()与get()方法
public void setAccountNo(String accountNo){
this.accountNo = accountNo;
}
public String getAccountNo(){
return accountNo;
}
//balance的set()与get()方法
public double getBalance(){
return balance;
}
public synchronized void draw(double drawAmount){
try{
if(!flag){
wait();
}else{
//执行取钱操作
balance -= drawAmount;
System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + "--账户余额为 " + balance);
flag = false;
//唤醒其它
notifyAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
}
public synchronized void deposit(double depositAmount){
try{
//如果flag为true,表明账户中有钱,存钱方法阻塞
if(flag){
wait();
}else{
//执行存钱操作
balance += depositAmount;
System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount + "--账户余额为:" + balance);
flag = true;
notifyAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
}
}
class DrawThread extends Thread
{
//用户账号
private Account account;
private double drawAmount = 100;
public DrawThread(String name, Account account){
super(name);
this.account = account;
}
public void run(){
while(true){
account.draw(drawAmount);
}
}
}
//业务模型与运转模型耦合在一起
class DepositThread extends Thread
{
//用户账号
private Account account;
private double depositAccount = 100;
public DepositThread(String name, Account account){
super(name);
this.account = account;
}
public void run(){
while(true){
account.deposit(depositAccount);
}
}
}
public class ThreadComTest
{
public static void main(String[] args){
//创建账户
Account account = new Account("232323",0);
new DepositThread("存钱者",account).start();
new DrawThread("取钱者",account).start();
}
}
2.使用Condition控制线程通信
当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。
Condition将同步监视器方法(wait()、notify()、notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集。如此一来,Lock替代同步方法或同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对应的newCondition()方法即可。
Condition类提供如下3个方法:
1)await()
类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signaAll()方法来唤醒该线程。该await()方法有多种变种:如long awaitNanos(long nanosTimeout)、void awaitUninterruptibly()、awaitUntil(Date deadline)等,可以完成更丰富的等待操作。
2)signal()
唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该Lock对象的锁定后(await()方法),才可以执行被唤醒的线程。
3)signalAll()
唤醒在此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后才可以执行被唤醒的线程。
代码有点长,需要认真研究。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class Account
{
//定义账户、账户余额
private String accountNo;
private double balance;
//标识账户中是否已有存款
private boolean flag = false;
//显式定义Lock对象
private final Lock lock =new ReentrantLock();
private final Condition condition = lock.newCondition();
//构造器
public Account(String accountNo, double balance){
this.accountNo = accountNo;
this.balance = balance;
}
//accountNo的set()与get()方法
public void setAccountNo(String accountNo){
this.accountNo = accountNo;
}
public String getAccountNo(){
return accountNo;
}
//balance的set()与get()方法
public double getBalance(){
return balance;
}
public void draw(double drawAmount){
lock.lock();
try{
if(!flag){
condition.await();
}else{
//执行取钱操作
balance -= drawAmount;
System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + "--账户余额为 " + balance);
flag = false;
//唤醒其它
condition.signalAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
finally{
lock.unlock();
}
}
public void deposit(double depositAmount){
lock.lock();
try{
//如果flag为true,表明账户中有钱,存钱方法阻塞
if(flag){
condition.await();
}else{
//执行存钱操作
balance += depositAmount;
System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount + "--账户余额为:" + balance);
flag = true;
condition.signalAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}finally{
lock.unlock();
}
}
}
class DrawThread extends Thread
{
//用户账号
private Account account;
private double drawAmount = 100;
public DrawThread(String name, Account account){
super(name);
this.account = account;
}
public void run(){
while(true){
account.draw(drawAmount);
}
}
}
//业务模型与运转模型耦合在一起
class DepositThread extends Thread
{
//用户账号
private Account account;
private double depositAccount = 100;
public DepositThread(String name, Account account){
super(name);
this.account = account;
}
public void run(){
while(true){
account.deposit(depositAccount);
}
}
}
public class ThreadComTest
{
public static void main(String[] args){
//创建账户
Account account = new Account("232323",0);
new DepositThread("存钱者",account).start();
new DrawThread("取钱者",account).start();
}
}