重入锁 ReentrantLock 是 Java 1.5 引入的,重入的意思是指可以重复获取锁,即拿到锁的对象可以再次拿一次锁,而不必先释放上一个锁。
ReentrantLock 实现了 Lock 接口。用 ReentrantLock 保护代码块的结构如下:
private void dosomethingLock() { Lock lock = new ReentrantLock(); lock.lock(); try { dosomething(); } finally { lock.unlock(); } }这一结构保证了任何时候只有一个线程进入临界区,临界区就是在同一个时刻只有一个任务可以访问的代码区。unlock 操作放在 finally 可以保证锁一定可以被释放。
如果进入临界区时,发现在某一个条件满足之后才能执行,这时可以使用一个条件对象 Condition 来管理那些已经获得了锁但是却不能做有用工作的线程。
下面用一个转账的例子说明重入锁和条件对象。
public class Alipay { private double[] accounts; private Lock alipayLock; public Alipay(int n, double money) { accounts = new double[n]; alipayLock = new ReentrantLock(); for (double a : accounts) { a = money; } } }以上代码定义了一个 Alipay 用来管理 n 个账户,其中有一个重入锁 alipayLock。
接着定义一个转账方法 transfer。
public void transfer(int from, int to, int amount) { alipayLock.lock(); try { while (accounts[from] < amount) { condition.await(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { alipayLock.unlock(); } }如果转账的金额大于当前账户的余额,那么条件对象 condition 就 await 等待,直到其他账户先转入给它足够的余额。这里的 condition 是通过 ReentrantLock 的 newCondition 方法构造的。
调用了 condition 的 await 后就会放弃锁,同时等待,进入该 condition 的等待集。一旦有其他线程调用了同一个条件对象的 signalAll 方法后,就会重新激活因为这个条件等待的所有的线程。
public void transfer(int from, int to, int amount) { alipayLock.lock(); try { while (accounts[from] < amount) { condition.await(); } accounts[from] -= amount; accounts[to] += amount; condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { alipayLock.unlock(); } }调用 signalAll 方法时并不是立即激活了一个等待线程,而是解除了等待线程的阻塞,让它们继续往下执行。还有一个方法是 signal,它是解除某一个线程的阻塞,如果该线程仍然不能运行,则再次被阻塞,如果没有其他线程再次调用 signal,那么系统就死锁了。
最终的代码如下,账户 1 依靠账户 0 的转账使得账户 1 有足够的余额转给账户 2。
public class Alipay { private double[] accounts; private Lock alipayLock; private Condition condition; public Alipay(int n, double money) { accounts = new double[n]; alipayLock = new ReentrantLock(); condition = alipayLock.newCondition(); for (int i = 0; i < n; ++i) { accounts[i] = money; } } public void transfer(int from, int to, int amount) { alipayLock.lock(); try { while (accounts[from] < amount) { System.out.println("no enough, await: " + Thread.currentThread().getName()); condition.await(); } accounts[from] -= amount; accounts[to] += amount; condition.signalAll(); System.out.println("transfer ok, signal all:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { alipayLock.unlock(); } } public static void main(String[] args) throws InterruptedException { Alipay alipay = new Alipay(3, 20); new Thread(new Runnable() { @Override public void run() { System.out.println("transfer 1 to 2, amount 30"); alipay.transfer(1, 2, 30); } }).start(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("transfer 0 to 1, amount 10"); alipay.transfer(0, 1, 10); } }).start(); } }以上代码输出结果如下:
transfer 1 to 2, amount 30 no enough, await: Thread-0 transfer 0 to 1, amount 10 transfer ok, signal all:Thread-1 transfer ok, signal all:Thread-0可以看出账户 0 的转账调用了 signalAll,这时线程 Thread-0 收到信号继续执行,当它发现余额足够转出就跳出循环,往下执行。