银行取钱问题
public void draw (double drawAmount) {
if (account.getBalance() >= drawAmount) { // (1)
account.setBalance(account.getBalance() -= drawAmount); // (2)
System.out.println ("余额为:" + account.getBalance() ) ;
}
}
由于线程并发执行的不确定性,此时有可能发生当甲乙两条线程同时取钱时,甲线程运行完 (1) 语句还未执行 (2) 语句时,balance 此时还满足条件,然后乙线程也执行完 (1) 语句,并且执行完 (2) 语句,然后 balance 变成不满足条件了,但是甲线程已经执行过 (1) 语句了,它也接着执行 (2) 语句,balance 出现负数。但是银行怎么可以让你超额取钱呢,所以就要使用同步机制,限制同一个账户当有人在取钱时,别人便不能取钱。
同步代码块
public void draw (double drawAmount) {
// 使用 synchronized 保证以下代码只有一个线程在执行,这里 account 对象是同步监听器
synchronized (account) {
if (account.getBalance() >= drawAmount) {
account.setBalance(account.getBalance() -= drawAmount);
System.out.println ("余额为:" + account.getBalance() ) ;
}
}
// 同步代码块结束,释放同步锁
}
同步方法
// 使用 synchronized 修饰需要同步的方法,这里的同步监听器是调用该方法的对象,即 this
public synchronized void draw (double drawAmount) {
if (account.getBalance() >= drawAmount) {
account.setBalance(account.getBalance() -= drawAmount);
System.out.println ("余额为:" + account.getBalance() ) ;
}
}
同步锁
// 首先需要在类中定义一把锁
private final ReentrantLock lock = new ReentrantLock () ;
public void draw (double drawAmount) {
// 对同步锁进行加锁,这里 lock 对象使用隐式的同步监听器
lock.lock () {
try {
if (account.getBalance() >= drawAmount) {
account.setBalance(account.getBalance() -= drawAmount);
System.out.println ("余额为:" + account.getBalance() ) ;
}
// 使用 finally 块来确保释放锁
} finally {
lock.unlock () ;
}
}
}
关于同步机制
对象给自己建了一道门,门上有把锁,同步监听器就相当于这把锁,保护着门里的资源只能一条线程访问,每条线程都持有一把万能钥匙,当遇到门时,首先寻找门上的锁,若门上的锁不在,这门就成了墙,线程进不去,就阻塞了,直到别的线程将锁放回去,线程看到锁后就用自己的钥匙开启这把锁进去,并带走这把锁让别的线程进不来,运行结束后便会释放这把锁。
每个对象只有一把锁,但每条线程可以拥有很多锁,意思就是线程进入门内又发现一把锁,可以继续用自己的钥匙开这把锁并带走锁,那么该线程就拥有了两把锁。
线程同步会降低程序的运行效率。
不要对线程安全类的所有方法都同步,只对那些会改变竞争资源的方法进行同步。
可变类在单线程环境中使用线程非安全版以保证性能,在多线程环境中使用线程安全版。(例如:StringBuilder 、HashMap 和 ArrayList 属于线程非安全,它们分别有相对应的线程安全版 StringBuffer 、Hashtable 和 Vector )
线程通信
使用条件变量控制线程协调运行
对于 synchronized 有 wait() , notify() ,notifyAll() 这三种方法,这三种由同步监听器对象来调用。
对于 Lock 锁有 await() , signal() , signalAll() 这三种方法,这三种方法也由同步监听器 Condition 对象调用。
假设有甲乙两条线程,甲线程负责存钱,乙线程负责取钱,有一个定额账户,额度满时便不能存钱。
class Account {
private String accountNum ;
private double balance ;
public Account(String accountNum, double balance) {
this.accountNum = accountNum ;
this.balance = balance ;
}
public double getBalance() {
return this.balance ;
}
public void setBalance (double balance) {
this.balance = balance ;
}
public synchronized void draw (double drawAmount) {
try {
if (balance < drawAmount) {
// 让当前线程等待,直到其他同步监听器的 notify() 方法或 notifyAll() 方法来唤醒该线程
wait();
} else {
balance -= drawAmount ;
System.out.println("取款后余额为:" + balance);
// 唤醒在此同步监听器上等待所有线程,只有当前线程放弃对该同步监听器的锁定后(使用 wait() 方法),才可以执行被唤醒的线程
notifyAll();
}
} catch (Exception e) {
e.printStackTrace() ;
}
}
public synchronized void deposit (double depositAmount) {
try {
if (balance == 5) {
wait();
} else {
balance += depositAmount ;
System.out.println("存款后余额为:" + balance);
notifyAll();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class DrawRunnable implements Runnable {
private Account account;
private double drawAmount;
public DrawRunnable(Account account, double drawAmount){
this.account = account;
this.drawAmount = drawAmount;
}
public void run() {
for (int i=0;i<20;i++) {
account.draw(drawAmount);
}
}
}
class DepositRunnable implements Runnable {
private Account account;
private double depositAmount;
public DepositRunnable(Account account, double depositAmount){
this.account = account;
this.depositAmount = depositAmount;
}
public void run() {
for(int i=0;i<20;i++){
account.deposit(depositAmount);
}
}
}
public class Test {
public static void main(String[] args){
Account acct = new Account ("1234567", 0);
DrawRunnable drawer = new DrawRunnable(acct, 1);
DepositRunnable depositer = new DepositRunnable(acct, 1);
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(drawer);
pool.execute(depositer);
}
}