ReentrantLock 公平锁源码 第0篇(lock公平锁和非公平 源码锁)

  本篇文章为你整理了ReentrantLock 公平锁源码 第0篇(lock公平锁和非公平 源码锁)的详细内容,包含有reentrantlock公平锁和非公平锁原理 lock公平锁和非公平 源码锁 reentrantlock公平锁和非公平锁优缺点 synchronize公平锁 ReentrantLock 公平锁源码 第0篇,希望能帮助你了解 ReentrantLock 公平锁源码 第0篇。

  ReentrantLock 0

  关于ReentrantLock的文章其实写过的,但当时写的感觉不是太好,就给删了,那为啥又要再写一遍呢

  最近闲着没事想自己写个锁,然后整了几天出来后不是跑丢线程就是和没加锁一样,而且五六段就一个cas性能很差,感觉离大师写的差十万八千里

  于是!我就想重新研究研究看看大师咋写的,这篇博客也算个笔记吧,这篇看的是ReentrantLock的公平锁,准备写个两三篇关于ReentrantLock 就这两天写!

  这篇博客完全个人理解,如果有不对的地方欢迎您评论或者私信我,我非常乐意接受您的意见或建议

  首先要知道,ReentrantLock是基本都是在java代码层面实现的,而最主要的一个东西就是CAS compare and swap 比较并交换

  这个操作可以看成为是一个原子性的,在java中可以使用反射获取到Unsafe类来进行cas操作

  

public test() {

 

   try {

   Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");

   if (!unsafeField.isAccessible()) {

   unsafeField.setAccessible(true);

   unsafe = (Unsafe) unsafeField.get(null);

   } catch (NoSuchFieldException IllegalAccessException e) {

   e.printStackTrace();

  

 

  在juc包下LockSupport类中有两个方法park和unpark 这两个就像是wait和notify/notifyAll,但是又不相同,可以暂时理解为就是暂停线程和启动线程

  详细的介绍可以看看这个博客 : https://www.jianshu.com/p/da76b6ab56be

  关于如何使用ReentrantLock就不在赘述了直接开始看代码,本来是想把类的关系图放这的,但是我的idea好像有点问题,你们可以自己打开idea看,ctrl+alt+u打开类的关系图

  

public static void main(String[] args) {

 

   ReentrantLock lock = new ReentrantLock(true);

   lock.lock();

   lock.unlock();

  

 

  

public ReentrantLock(boolean fair) {

 

   sync = fair ? new FairSync() : new NonfairSync();

  

 

  lock方法

  

public void lock() {

 

   sync.lock();

  

 

  点到里面实际调用的是FairSync类中的lock()方法

  

final void lock() {

 

   acquire(1);

  

 

  

public final void acquire(int arg) {

 

   if (!tryAcquire(arg)

   acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

   selfInterrupt();

  

 

  tryAcuqire方法

  

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;

  

 

  首先是获取了当前的线程,之后有个getState,这个方法返回的是当前这个锁的状态

  

protected final int getState() {

 

   return state;

  

 

  hasQueuedPredecessors

  先来看Node,这个Node是一个组成双向链表的实体类,几个重要的属性

  

volatile int waitStatus;

 

  volatile Node prev;

  volatile Node next;

  volatile Thread thread;

  Node nextWaiter;

  

 

  waitStatus 存放当前结点的状态

  prev 存放上个结点

  next 存放下个结点

  thread 存放线程

  nextWaiter 翻译是下个服务员,我理解是为下个节点服务,后面咱们细说

  

public final boolean hasQueuedPredecessors() {

 

   Node t = tail;

   Node h = head;

   Node s;

   return h != t

   ((s = h.next) == null s.thread != Thread.currentThread());

  

 

  这里有两个属性,tail尾结点,head头结点,之后下面一个判断分别是

  头结点不等于尾结点 并且 (头结点的下一结点不等于null或者头结点后面一个结点的线程不等于当前线程)

  

if (c == 0) {

 

   if (!hasQueuedPredecessors() compareAndSetState(0, acquires)) {

   setExclusiveOwnerThread(current);

   return true;

  

 

  在hasQueuedPredecessors()接着就是一个cas,修改的这个锁的状态

  如果成功,则调用setExclusiveOwnerThread()

  

protected final void setExclusiveOwnerThread(Thread thread) {

 

   exclusiveOwnerThread = thread;

  

 

  将当前线程存放到exclusiveOwnerThread属性中

  那么在没有冲突的情况下lock的方法就走完了,现在咱们假设只有一个线程,从头来捋一下加锁的过程

  咱们顺着逻辑捋一下,从最开始的lock()方法开始,前面的就不写了,直接到acquire

  

public final void acquire(int arg) {

 

   if (!tryAcquire(arg)

   acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

   selfInterrupt();

  

 

  进入acquire

  

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;

  

 

  因为这个getState()方法获取的是属性state 而这个属性又没有其他的赋值操作,初始化就是0,进入if(c==0)

  之后进入hasQueuedPredecessors()

  

public final boolean hasQueuedPredecessors() {

 

   Node t = tail;

   Node h = head;

   Node s;

   return h != t

   ((s = h.next) == null s.thread != Thread.currentThread());

  

 

  首先第一个判断就已经是false了,因为tail和head都没有进行过初始化,都是null,所以等于,直接返回 false,而在hasQueuedPredecessors()方法前面还有一个!取反为true,直接进入if代码块

  设置完exclusiveOwnerThread属性后就return true,走出lock()方法,加锁方法结束

  exclusiveOwnerThread属性存放的是当前那个线程在占有锁

  这是在没有线程获取锁冲突的情况下,如果现在两个线程同时来的话,还是看tryAcquire方法

  

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;

  

 

  咱们现在假设线程A获取到锁,去执行业务代码了,线程B进入

  getState()获取的值就不在是0了,因为线程A执行完compareAndSetState(0, acquires)改的就是getState()方法获取的state属性

  那么进入else if (current == getExclusiveOwnerThread()) 哎这个不是获取当前占有锁的那个线程的方法吗,是的

  那为什么有这个判断呢,ReentrantLock的特性 重入锁,啥叫重入锁?:同一个线程可以多次获取同一个锁 例如下面的例子

  

public class Test{

 

   private static final ReentrantLock LOCK=new ReentrantLock(true);

   public void a(){

   LOCK.lock();

   b();

   LOCK.unlock();

   public void b(){

   LOCK.lock();

   //xxxxxx

   LOCK.unlock();

  

 

  如果没有重入锁的特性,那么这个方法是不是就死锁了呢?,假设当我们一个线程去调用a方法时..

  
........

  好了回到代码中,就是将锁持有的状态+1,设置后返回true,因为我们现在是B线程,所以这个if不成立,返回false

  回到acquire方法

  

public final void acquire(int arg) {

 

   if (!tryAcquire(arg)

   acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

   selfInterrupt();

  

 

  因为tryAcquire为false,取反继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

  addWaiter

  先来看里面的addWaiter方法吧,传了一个参数Node.EXCLUSIVE

  

static final Node EXCLUSIVE = null;

 

  

 

  这个参数是Node类中的一个属性

  

private Node addWaiter(Node mode) {

 

   //创建一个Node

   Node node = new Node(Thread.currentThread(), mode);

   Node pred = tail;

   if (pred != null) {

   node.prev = pred;

   if (compareAndSetTail(pred, node)) {

   pred.next = node;

   return node;

   enq(node);

   return node;

  

 

  Node的有参构造如下

  

Node(Thread thread, Node mode) { // Used by addWaiter

 

   this.nextWaiter = mode;

   this.thread = thread;

  

 

  首先是创建了一个Node结点,之后判断如果tail结点不为null,因为A线程走完tryAcquire直接返回了,tail和head都是null,所以if不成立,进入enq方法

  

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,因为我们的假设就两个线程,A线程已经去执行业务了,所以进去第一个if

  通过cas来设置头节点为一个new Node() 注意!这里是新new的Node,设置完后将头在设置给尾,那么此时的节点关系如下

  em?? 咱们这B节点也没有添加进链表啊,别急,看看上面的for(;;)

  在下次循环的时候tail还等于null吗?答案是否定的

  之后还是头节点赋值给t,将B节点的上一个设置为t,cas设置tail,成功后t节点的next设置为B节点,返回t (返回的值其实没接收)

  挺简单的逻辑说的太费劲了,还是看图吧,执行完第二遍for后节点关系如下

  这个enq方法是百分百能确定这个节点已经添加进去的,因为你不添加进去就出不来这个方法,那么返回addWaiter方法,走完这个enq就还有一句话,return node;

  acquireQueued

  

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);

  

 

  那么一进来就定义了一个failed用来处理如果发生错误需要将链表中错误的节点移除,咱先不看

  之后的一个interrupted存放是否被打断过,继续发现还是一个for(;;),第一步执行了node.predecessor()

  

final Node predecessor() throws NullPointerException {

 

   Node p = prev;

   if (p == null)

   throw new NullPointerException();

   else

   return p;

  

 

  也就是先获取了下B节点的上一个,也就是那个线程为null的空节点(注意:不是null,而是一个空的Node)

  判断上个节点是不是head,如果是,则尝试获取锁

  假设现在A线程还是没有完成业务代码,没有执行unlock(),那么我们进入下个if,

  

if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt())

 

   interrupted = true;

  

 

  shouldParkAfterFailedAcquire

  

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

 

   int ws = pred.waitStatus;

   if (ws == Node.SIGNAL)

   return true;

   if (ws 0) {

   do {

   node.prev = pred = pred.prev;

   } while (pred.waitStatus

   pred.next = node;

   } else {

   compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

   return false;

  

 

  代码不多,但是不太好理解 开始还是获取B节点上个节点的状态,也就是那个空节点,因为咱们一路代码跟过来的,没有看到哪里对空节点的state属性进行过修改,所以它还是0

  那么第一个判断

  

static final int SIGNAL = -1; //Node类中的属性

 

  

 

  空节点的状态是否为-1,显然不是,if(ws 0)也不会进,直接进入else,cas修改空节点的状态,改为了-1,然后返回

  

if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt())

 

   interrupted = true;

  

 

  因为是 阻断了后面的方法,所以不进入,那么本次循环结束,最外层是个for(;;)所以下次循环开始

  我们还是假设通过tryAcquire()没有获取到锁,又进入了shouldParkAfterFailedAcquire

  那么这次第一个if我们就会进去,因为上次循环已经将B节点前面的一个空节点的状态改为-1了,return true

  回到if那么就进入parkAndCheckInterrupt方法

  

private final boolean parkAndCheckInterrupt() {

 

   LockSupport.park(this);

   return Thread.interrupted();

  

 

  park,那么B线程就停在这里了,把目光回到A线程,它终于执行完业务代码了,执行unlock

  unlock

  

public void unlock() {

 

   sync.release(1);

  

 

  

public final boolean release(int arg) {

 

   if (tryRelease(arg)) {

   Node h = head;

   if (h != null h.waitStatus != 0)

   unparkSuccessor(h);

   return true;

   return false;

  

 

  看第一个if中的tryRelease

  

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;

  

 

  第一件事就是将锁的状态-1,因为它重入一次就+1,这也是为什么调用lock多少次就需要调用unlock多少次,因为要保证锁的状态为0

  之后判断加锁的线程和解锁的线程是不是同一个,不是抛出异常

  boolean free = false;这个来标识这个锁是不是已经没有人持有了,因为A线程就调用了一次lock,所以if(c==0)成立

  将free 改为true后将当前持有锁的线程设置为null,设置锁的状态,返回true,回到release方法

  因为返回true,所以进入if,判断head节点不为空,并且头节点的状态不为0

  那么头节点是空的吗? -不是 因为B节点在初始化链表是添加了一个空的节点(再说一遍不是null!是空的Node节点)

  那么头节点的状态是0吗? -不是 我们在第二次执行shouldParkAfterFailedAcquire()方法时已经将头节点的状态设置为-1了

  那么进入unparkSuccessor()

  

 private void unparkSuccessor(Node node) {

 

   int ws = node.waitStatus;

   if (ws 0)

   compareAndSetWaitStatus(node, ws, 0);

   Node s = node.next;

   if (s == null s.waitStatus 0) {

   s = null;

   for (Node t = tail; t != null t != node; t = t.prev)

   if (t.waitStatus = 0)

   s = t;

   if (s != null)

   LockSupport.unpark(s.thread);

  

 

  获取,cas将头节点的状态赋值为0,获取头节点的下一个节点,也就是我们的B节点,那么if (s == null s.waitStatus 0) 为false,走最下面的if(s!=null)

  就一句话 unpark(s.thread)

  B线程被唤醒后回到acquireQueued方法

  

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);

  

 

  因为是个死循环,那么继续走if的判断if (p == head tryAcquire(arg))这个时候tryAcquire方法就可以获取到锁,进入if,设置head并清除引用后方法结束

  到此,AB线程都走完了

  篇幅有点长了,这两天再接着写,拜拜

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: