Java并发--ReentrantLock原理详解
ReentrantLock是什么?
- ReentrantLock重入锁,递归无阻塞的同步机制,实现了Lock接口;
- 能够对共享资源重复加锁,即当前线程获取该锁,再次获取不会被阻塞;
- 支持公平锁和非公平锁。
UML图
- 公平锁
2.非公平锁
问题:
- 重入性的实现原理;
- 公平和非公平锁。
ReentrantLock的使用
//非公平锁 ReentrantLock lock = new ReentrantLock(); lock.lock(); //lock方法: public void lock() { sync.lock(); }
注释:
- Sync为ReentrantLock里面的一个内部类,它继承AQS(AbstractQueuedSynchronizer),它有两个子类:公平锁FairSync和非公平锁NonfairSync。
- ReentrantLock里面大部分的功能都是委托给Sync来实现的,同时Sync内部定义了lock()抽象方法由其子类去实现,默认实现了nonfairTryAcquire(int acquires)方法,可以看出它是非公平锁的默认实现方式。下面我们看非公平锁的lock()方法:
final void lock() { //尝试获取锁 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else //获取失败,调用AQS的acquire(int arg)方法 acquire(1); }
- 首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)方法,该方法定义在AQS中,如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
- 这个方法首先调用tryAcquire(int arg)方法,在AQS中讲述过,tryAcquire(int arg)需要自定义同步组件提供实现,非公平锁实现如下:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { //当前线程 final Thread current = Thread.currentThread(); //获取同步状态 int c = getState(); //state == 0,表示没有该锁处于空闲状态 if (c == 0) { //获取锁成功,设置为当前线程所有 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //线程重入 //判断锁持有的线程是否为当前线程 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
释放锁
- 获取同步锁后,使用完毕则需要释放锁,ReentrantLock提供了unlock释放锁:
public void unlock() { sync.release(1); }
- unlock内部使用Sync的release(int arg)释放锁,release(int arg)是在AQS中定义的:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
- 与获取同步状态的acquire(int arg)方法相似,释放同步状态的tryRelease(int arg)同样是需要自定义同步组件自己实现:
protected final boolean tryRelease(int releases) { //减掉releases int c = getState() - releases; //如果释放的不是持有锁的线程,抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //state == 0 表示已经释放完全了,其他线程可以获取同步状态了 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
只有当同步状态彻底释放后该方法才会返回true。当state == 0 时,则将锁持有线程设置为null,free= true,表示释放成功。
重入性的实现原理
- 在线程获取锁时,如果已经获取锁的线程是当前线程,则直接再次获取成功;
- 锁会被获取n次,只有锁在被释放同样n次之后,该锁才会被完全释放。
重入锁--非公平锁--加锁的实现原理
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair tryfor trylock method. */ 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) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
注释:如果该锁未被任何线程占有,该锁能被当前线程获取,如果该锁被占有,检查占有线程是否是当前线程,如果是的话,同步状态加一并返回true。
重入锁--非公平锁--解锁的实现原理
源代码java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
protected final boolean tryRelease(int releases) { //当前线程的同步状态减一 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { //只用同步状态为0的时候,锁才能释放成功,返回true free = true; setExclusiveOwnerThread(null); } //当锁未完全释放,返回false setState(c); return free; }
注释:
当前线程的同步状态减一, 只用同步状态为0的时候,锁才能释放成功,返回true, 当锁未完全释放,返回false。
公平性锁
锁的获取顺序符合请求上的绝对时间顺序,满足FIFO,
- ReentrantLock的构造方法,无参时是构造非公平锁,源码为:
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); }
- ReentrantLock的构造方法,带boolean参数,可传入一个boolean值,true时为公平锁,false时为非公平锁,源码为:
/** * Creates an instance of {@codeReentrantLock} with the * given fairness policy. * * @paramfair {@codetrue} ifthis lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
重入锁--公平锁--加锁的实现原理
源代码:java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
/** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ 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; }
注释:
public final boolean hasQueuedPredecessors() { Node t = tail; //尾节点 Node h = head; //头节点 Node s; //头节点 != 尾节点 //同步队列第一个节点不为null //当前线程是同步队列第一个节点 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } //该方法主要做一件事情:主要是判断当前线程是否位于CLH同步队列中的第一个。如果是则返回true,否则返回false。
- 代码和非公平锁基本一直,唯一的不同在于增加了hasQueuedPredecessors的逻辑判断。该方法用来判断当前节点在同步队列中是否有前驱节点。如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点,猜能够做后面的逻辑判断,
- 公平锁每次都是从同步队列中的对一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能够再次获取到锁。
公平锁和非公平锁的比较
- 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
- 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。
参考链接: