可重入锁指的是假设一个线程已经获得了一个锁,那么它能够多次进入这个锁,当然前提是线程须要先获得这个锁。
可重入锁是最常使用的锁。Java的内置锁就是可重入锁,使用synchronizedkeyword能够启用内置锁机制,比方说一个类有两个synchronized方法A和B。在A方法中调用了B方法,假设锁不是可重入的。那么訪问B时须要再次竞争锁。这样会带来死锁。
public synchronized void A(){ B(); } public synchronized void B(){ }
可重入锁攻克了这个问题,它使用一个计数器来记录一个线程进入锁的次数,每次进入锁计数器就加1。释放锁减1。直到计数器为0时表示真正释放了锁。其它锁看到计数器不为0时就知道有其它线程已经获得了锁。就须要等待。Java的内置锁的基本原理也是这样,JDK1.5之后提供了显式锁ReentrantLock也是这个基本原理。
以下实现一个简单的可重入锁。
1. 使用一个Thread引用指向获得锁的线程
2. 使用一个计数器记录一个线程进入锁的次数,当计数器为0时表示锁是空暇的
3. 使用一个内部锁Lock来同步线程
4. 使用一个isHoldZero的条件来进行条件队列操作
5. 当获得锁的线程是自己时,仅仅改动计数器的值,直接获得锁
6. 当获得锁的线程不是自己时。须要在holdCount !=0 这个条件谓词上等待。直到计数器归0,再次竞争锁
7. 释放锁时计数器减1,当计数器为0时。唤醒在条件队列中等待的线程
package com.zc.lock; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 简单的可重入锁实现,使用一个计数器记录当前线程重入锁的次数,获得锁时计数器加1,释放锁时计数器减1。当计数器等于0时表示释放了锁 * **/ public class SimpleReentrantLock implements Lock{ // 指向已经获得锁的线程 private volatile Thread exclusiveOwnerThread; // 记录获取了同一个锁的次数 private volatile int holdCount; private final java.util.concurrent.locks.Lock lock; // 是否是自己获得锁的条件 private final Condition isCountZero; public SimpleReentrantLock(){ lock = new ReentrantLock(); isCountZero = lock.newCondition(); holdCount = 0; } @Override public void lock() { lock.lock(); try{ // 当前线程的引用 Thread currentThread = Thread.currentThread(); // 假设获得锁的线程是自己,那么计数器加1,直接返回 if(exclusiveOwnerThread == currentThread){ holdCount ++; return; } while(holdCount != 0){ try { isCountZero.await(); } catch (InterruptedException e) { throw new RuntimeException("Interrupted"); } } // 将exclusiveOwnerThread设置为自己 exclusiveOwnerThread = currentThread; holdCount ++; }finally{ lock.unlock(); } } @Override public void unlock() { lock.lock(); try{ holdCount --; if(holdCount == 0){ isCountZero.signalAll(); } }finally{ lock.unlock(); } } }