在前面的一篇中,总结了如何使用Lock和Condition对象。我们先总结一下有关锁和条件的关键之处:
- 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
- 锁可以管理试图进入被保护代码段的线程。
- 锁可以拥有一个或多个相关的条件对象。
- 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
synchronized关键字
Java中的每一个对象都有一个内部锁;如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。内部对象锁只有一个相关条件,wait方法添加一个线程到等待集中,notifyAll/notify方法解除等待线程的阻塞状态。
使用synchronized的方式修改Bank类:
/**
* @author XzZhao
*/
public class Bank {
private final double[] accounts;
public Bank(int n, double initialBalance) {
accounts = new double[n];
for (int i = 0; i < accounts.length; i++) {
accounts[i] = initialBalance;
}
}
public synchronized void transfer(int from, int to, double amount) throws InterruptedException {
while (accounts[from] < amount) {
wait(); // 将该线程放到条件的等待集中
}
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance());
notifyAll(); // 解除该条件的等待集中的所有线程的阻塞状态
}
public synchronized double getTotalBalance() {
double sum = 0;
for (double a : accounts) {
sum += a;
}
return sum;
}
public int size() {
return accounts.length;
}
}
用synchronized关键字来写代码会简洁一些。每一个对象有一个内部锁,并且该锁有一个内部条件,由锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程。
内部锁和条件存在的局限
- 不能中断一个正在试图获得锁的线程
- 试图获得锁时不能设定超时
- 每个锁只有单一的条件,可能是不够的
同步阻塞
每一个对象有一个锁。线程可以通过调用同步方法获得锁。还有另一种方式获得锁,通过进入一个同步阻塞。
获得obj对象的锁:
synchronized (obj) {
work code
}
Volatile域
有时候,仅仅为了读写一个或两个实例域就使用同步,显得开销过大了。volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
例如:
private volatile boolean result;
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
final变量
除非使用锁或者volatile修饰符,否则无法从多个线程安全地读取一个域。还有一种情况可以安全地访问一个共享域,就是当这个域声明为final的时候。
死锁
有可能会因为每一个线程要等待更多的钱款存入而导致所有线程都被阻塞。这样的状态称为死锁。注意还有一种很容易导致死锁的情况:就是在解除等待线程的阻塞状态时,如果只对一个线程解锁,而不是全部等待的线程。(signal 或notify)它仅仅为一个线程解锁,而且,它很可能选择一个不能继续运行的线程,这样就会出现死锁。
读/写锁
使用读/写锁的必要步骤:
- 构造一个ReentrantReadWriteLock对象:
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
- 获取读锁和写锁:
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock readLock = rwl.readLock();
private final Lock writeLock = rwl.writeLock();
- 对所有的获取方法加读锁:
public double getTotalBalance(){
readLock.lock();
try{
...
}finally{
readLock.unlock();
}
}
- 对所有的修改方法加写锁:
public void transfer(){
writeLock.lock();
try{
...
}finally{
writeLock.unlock();
}
}