重入锁ReentrantLock
- 支持一个线程对同一个资源进行多次加锁。
- 支持获取锁时的公平和非公平性选择
锁获取的公平性问题:
先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。ReentrantLock提供了一个构造函数(传人一个布尔值),来控制锁是否是公平的
1.实现重进入
- 锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
- 线程重复n次获取了锁,随后在第n次释放该锁后,其他线程才能够获取到该锁。锁被获取时,计数自增,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
//非公平获取锁
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires;//成功获取锁的线程再次获取锁,只是增加了同步状态值 if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
} return false; }
//释放锁
protected final boolean tryRelease(int releases) { int c = getState() - releases;//在释放同步状态时减少同步状态值 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
2.公平与非公平获取锁
公平与非公平获取锁的区别 :锁的获取顺序是否符合FIFO
//公平获取锁 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current);
return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; }
return false; }
该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁
测试公平和非公平锁在获取锁时的区别
public class FairAndUnfairTest { private static Lock fairLock = new ReentrantLock2(true); private static Lock unfairLock = new ReentrantLock2(false); @Test public void fair() { testLock(fairLock); }
@Test public void unfair() { testLock(unfairLock); }
private void testLock(Lock lock) { // 启动5个Job(略) }
private static class Job extends Thread { private Lock lock; public Job(Lock lock) { this.lock = lock; }
public void run() {// 连续2次打印当前的Thread和等待队列中的Thread(略) } }
private static class ReentrantLock2 extends ReentrantLock { public ReentrantLock2(boolean fair) { super(fair); }
public Collection<Thread> getQueuedThreads() { List<Thread> arrayList = new ArrayList<Thread>(super.getQueuedThreads()); Collections.reverse(arrayList); return arrayList; } } }
观察表5-6所示的结果(其中每个数字代表一个线程),公平性锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁出现了一个线程连续获取锁的情况。 由于刚释放锁的线程再次获取同步状态的几率会非常大 ,这样就减少了因锁切换而导致的线程上下文切换的开销