ReentrantLock是JDK1.5引入的,它拥有与synchronized相同的并发性和内存语义,并提供了超出synchonized的其他高级功能(例如,中断锁等候、条件变量等),并且使用ReentrantLock比synchronized能获得更好的可伸缩性
AQS(AbstractQueuedSynchronizer)主要利用硬件原语指令(CAS compare-and-swap),来实现轻量级多线程同步机制,并且不会引起CPU上文切换和调度,同时提供内存可见性和原子化更新保证(线程安全的三要素:原子性、可见性、顺序性)。
AQS的本质上是一个同步器/阻塞锁的基础框架,其作用主要是提供加锁、释放锁,并在内部维护一个FIFO等待队列,用于存储由于锁竞争而阻塞的线程。
ReentrantLock的实现核心是基于AQS(AbstractQueuedSynchronizer)来实现的.
先看ReentrantLock的构造方法和核心的类:
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = (fair)? new FairSync() : new NonfairSync(); } private final Sync sync; static abstract class Sync extends AbstractQueuedSynchronizer { ... } final static class NonfairSync extends Sync { ... } final static class FairSync extends Sync { ... }
通过构造方法可以看出:ReentrantLock 中的 Sync 继承自 AbstractQueuedSynchronizer, 其还有两个子类 NonfairSync 和 FairSync, 从构造方法中看出这两个子类决定了ReentrantLock的公平性问题。
这里提到一个锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线程最有机会获取锁,也可以说锁的获取是有序的。
非公平
// ReentrantLock.NonfairSync 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; }
公平:比较非公平的获取,仅加入了当前线程(Node)之前是否有前置节点在等待的判断hasQueuedPredecessors()
// ReentrantLock.FairSync 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; }
获取锁操作(先以非公平模式来看)
// ReentrantLock public void lock() { sync.lock(); } // ReentrantLock.NonfairSync final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // AbstractOwnableSynchronizer protected final void setExclusiveOwnerThread(Thread t) { exclusiveOwnerThread = t; } // AbstractQueuedSynchronizer public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // ReentrantLock.NonfairSync protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
通过原子的比较并设置操作,如果成功设置,说明锁是空闲的,当前线程获得锁,并把当前线程设置为锁拥有者;否则,调用acquire方法。
如果尝试以独占的方式获得锁失败,那么就把当前线程封装为一个Node,加入到等待队列中;如果加入队列成功,接下来检查当前线程的节点是否应该等待(挂起),如果当前线程所处节点的前一节点的等待状态小于0,则通过LockSupport挂起当前线程;无论线程是否被挂起,或者挂起后被激活,都应该返回当前线程的中断状态,如果处于中断状态,需要中断当前线程。
最后tryAcquire调用到最开始的那段非公平锁的代码中,处理逻辑如下:
1.如果锁状态空闲(state=0),且通过原子的比较并设置操作,那么当前线程获得锁,并把当前线程设置为锁拥有者;
2.如果锁状态空闲,且原子的比较并设置操作失败,那么返回false,说明尝试获得锁失败;
3.否则,检查当前线程与锁拥有者线程是否相等(表示一个线程已经获得该锁,再次要求该锁,这种情况叫可重入锁),如果相等,维护锁状态,并返回true;
4.如果不是以上情况,说明锁已经被其他的线程持有,直接返回false;
再看addWaiter方法
// AbstractQueuedSynchronizer private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
如果tail节点不为null,说明队列不为空,则把新节点加入到tail的后面,返回当前节点,否则进入enq进行处理;
// AbstractQueuedSynchronizer private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
如果tail节点为null,说明队列为空,需要建立一个虚拟的头节点,并把封装了当前线程的节点设置为尾节点;另外一种情况的发生,是由于在(1)中的compareAndSetTail可能会出现失败,这里采用for的无限循环,是要保证当前线程能够正确进入等待队列;
// AbstractQueuedSynchronizer final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
如果当前节点是队列的头结点(如果第一个节点是虚拟节点,那么第二个节点实际上就是头结点了),就尝试在此获取锁tryAcquire(arg)。如果成功就将头结点设置为当前节点(不管第一个结点是否是虚拟节点),返回中断状态。
检测当前节点是否应该park,如果应该park就挂起当前线程并且返回当前线程中断状态。
/** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3; private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
1.如果前一个节点的等待状态waitStatus<0,也就是前面的节点还没有获得到锁,那么返回true,表示当前节点(线程)就应该park()了。否则进行(2)。
2.如果前一个节点的等待状态waitStatus>0,也就是前一个节点被CANCELLED了,那么就将前一个节点去掉,递归此操作直到所有前一个节点的waitStatus<=0,进行(4)。否则进行(3)。
3.前一个节点等待状态waitStatus=0,修改前一个节点状态位为SINGAL,表示后面有节点等待你处理,需要根据它的等待状态来决定是否该park()。进行(4)。
4.返回false,表示线程不应该park()。
相关推荐
近日,阅读jdk并发包源码分析整理笔记。
AQS和ReentrantLock.pdf
前言 鼠年新的工作日开始了,新的一年新的工作学习,第一个工作日给自己定一些要求吧。 想写一个系列【隐匿撕源码...ReentrantLock源码 我们来看ReentrantLock的构造方法 有两种 传入boolean值 选择此锁是否是公平锁 默
7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf
ReentrantLock java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。这篇文章主要是从...
详细阐述了ReentrantLock通过AQS获取锁到释放锁的过程,附有关键方法的源码及注释
ReentrantLock Lock 加锁过程源码分析图,AQS 源码分析
ReentrantLock源码剖析
什么是公平锁和非公平锁 公平与非公平的一个很本质的区别就是,是否遵守FIFO(也就是先来后到)。当有多个线程来申请锁的时候,是否先申请的线程先获取锁,后申请的线程后获取锁?如果是的,则是 公平锁 ,否则是...
主要为大家详细介绍了Java并发系列之ReentrantLock源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
lock 最 常 用 的 类 就 是 ReentrantLock , 其 底 层 实 现 使 用 的 是 AbstractQueuedSynchronizer(AQS) 简单来说 AQS 会把所有的请求线程构成一个 CLH 队列,当一个线程执行完毕 (lock.unlock())时会激活...
主要介绍了ReentrantLock源码之条件锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面我们来一起学习一下吧
java语言 并发编程 ReentrantLock与synchronized区别 详解
本文档主要内容如下: 1、线程安全和锁 Synchronized 底层实现原理 2、可重入锁与 Synchronized 的其他特性 3、ThreadLocal 的底层实现与使用 ...11、AQS源码分析 12、CAS原理分析和使用场景 13、.....
JUC 包中的同步类基本都是基于 AQS 同步器来实现的,如 ReentrantLock,Semaphore 等。 二、原理 1、AQS 工作机制: 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为...
ReentrantLock的使用及注意事项
资源内容 基本概念:介绍并发编程的基本概念,如进程、线程、并行与并发的区别等。 同步机制:讲解如何使用锁、信号量、原子操作等同步机制来避免竞态条件和死锁。 并发模型:介绍不同的并发模型,如生产者-消费者...
第六章 ReentrantLock源码解析2--释放锁unlock()最常用的方式://注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁
带你看看Javad的锁-ReentrantLock前言ReentrantLock简介Synchronized对比用法源码分析代码结构方法分析SyncNonfairSyncFairSync非公平锁VS公平锁什么是公平非公平ReentrantLockReentrantLock的构造函数lock加锁方法...
1、ReentrantLock简介 2、ReentrantLock函数列表 3、重入的实现 4、公平锁与非公平锁 5、ReentrantLock 扩展的功能 6