ReentrantLock原理及源码阅读

ReentrantLock原理及源码阅读

1、ReentrantLock介绍

ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取到该锁,其他线程获取该锁的线程将会被阻塞而被放入该锁的AQS阻塞队列里面。

ReentrantLock最终还是使用AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁。

public ReentrantLock() { sync = new NonfairSync(); }
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */ abstract void lock(); .... }

其中Sync类直接继承自AQS,它的子类NonfairSyncFairSync分别实现了获取锁的公平和非公平策略

2、获取锁的方法

void lock()方法

当一个线程调用该方法时,说明该线程希望获取该锁,如果锁当前没有被其他线程占用,并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程,并设置AQS的状态值为1,然后直接返回。如果当前线程之前已经获取过该锁,则这次只是简单的把AQS的状态值加1后返回。如果该锁寂静被其他线程持有,则调用该方法的线程会被放入AQS队列后阻塞挂起。

public void lock() { sync.lock(); }

ReentranLock的lock委托给了sync类,根据创建ReentranLock的构造函数选择sync的实现是非公平还是公平,这个锁是一个非公平锁或公平锁。以下是sync的子类NonfairSync的情况,也就是非公平锁。

static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { // CAS 设置状态值state if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else // 获取失败则调用AQS的acquire方法 acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }

上述代码中,默认AQS状态值state为0,所以此一个调用lock的线程会通过CAS设置状态值为1,CAS成功则代表当前线程获取到了锁,然后通过setExclusiveOwnerThread设置该锁的持有者为当前线程。

protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; }

如果有其他线程调用lock方法获取该锁,CAS会失败,然后调用AQS的acquire方法,传递的参数为1.

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

AQS并没有实现tryAcquire方法,tryAcquire方法需要子类自己定制化,所以上面的tryAcquire实际上会调用ReentrantLock重写的tryAcquire方法。

protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 获取当前状态值。 int c = getState(); // 当前状态值为0则通过CAS获得锁 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; } // 为拿到锁返回false,然后该线程被放入AQS阻塞队列。 return false; }

非公平锁的体现:假设线程1调用lock方法时发现state状态值不为0,且当前线程不是锁的持有者则返回false,然后当前线程被放入AQS阻塞队列。这时线程2也调用了lock方法,发现当前状态值为0了(假设占有该锁的其他线程释放了该锁),所以通过CAS设置获取到了该锁。明明是线程1先请求获取的该锁,然而线程2获取到了锁,这就是非公平的体现。这里线程2获取锁前并没有查看当前AQS队列里面是否有比自己更早请求该锁的线程,而是使用了抢夺策略。

protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 当前state为0. 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; }

上述代码为公平锁的方法,公平锁的话只需要看FairSync重写的tryAcquire方法。

public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }

上面代码中,是实现公平性的核心代码,如果当前AQS队列为空或者当前线程是AQS的第一个节点则返回false。其中如果ht则说明当前队列为空,直接返回false。如果h!=t且snull则说明有一个元素将要作为AQS的第一个节点入队列,那么返回true,如果h!=t并且s!=null和s.thread !=Thread.currentThread则说明队列里面的第一个元素不是当前线程,那么返回true。

3、释放锁的方法

void unlock()方法
public void unlock() { sync.release(1); }

尝试释放锁,如果当前线程持有该锁,则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0,则当前线程会释放锁,否则仅仅只是减1而已。如果当前线程没有持有该锁而调用了该方法则会抛出异常。

protected final boolean tryRelease(int releases) { int c = getState() - releases; // 如果不是锁的持有者,则抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果当前可重入次数为0, 则清空锁持有线程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 设置可重入次数为原始值-1 setState(c); return free; }

__EOF__

本文作者Younger
本文链接https://www.cnblogs.com/youngerwb/p/16326577.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   YoungerWb  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示