ReentrantLock实现原理

一、ReentrantLock

ReentrantLock是基于AQS实现,AQS的基础又是CAS

ReentrantLock中有三个静态内部类,分别为继承了AQS的抽象内部类Sync,以及继承了Sync的内部类NonfairSync与FairSync分别代表不公平锁和公平锁

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.Collection;

//@since 1.5

public class ReentrantLock implements Lock, java.io.Serializable {
 
    private final Sync sync;
    //继承了Aqs的静态内部类
    abstract static class Sync extends AbstractQueuedSynchronizer {....}
	//得到非公平锁
    static final class NonfairSync extends Sync {....}
	//得到公平锁
    static final class FairSync extends Sync {....}
	//默认为非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
	//可以通过构建对象时传入的boolean来设定锁是否公平
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
	//获取锁
    public void lock() {
        sync.lock();
    }
	//尝试获取锁
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
	//尝试获取锁,参数为尝试的时间和时间单位
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
	//释放锁
    public void unlock() {
        sync.release(1);
    }
    ........
}

二、AbstractQueuedSynchronizer

类定义:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {}

构造器:

protected AbstractQueuedSynchronizer() { }

静态内部类Node

ReentrantLock实现的前提就是AbstractQueuedSynchronizer,简称AQS,是java.util.concurrent的核心,CountDownLatch、FutureTask、Semaphore、ReentrantLock等都有一个内部类是这个抽象类的子类

AQS内部有一个内部类Node,每个node都是一个节点

static final class Node{
    
		//表示Node处于共享模式
 		static final Node SHARED = new Node();
        //表示Node处于独占模式
        static final Node EXCLUSIVE = null;
    
     	//因为超时或者中断,Node被设置为取消状态,被取消的Node不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态,处于这种状态的Node会被踢出队列,被GC回收
        static final int CANCELLED =  1;
       //表示这个Node的继任Node被阻塞了,到时需要通知它
        static final int SIGNAL    = -1;
        //表示这个Node在条件队列中,因为等待某个条件而被阻塞 
        static final int CONDITION = -2;
       //使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
        static final int PROPAGATE = -3;
        
    	//0,新Node会处于这种状态 
        volatile int waitStatus;
        //队列中某个Node之前的Node 
        volatile Node prev;
		//队列中某个Node之后的Node 
        volatile Node next;
    
        //这个Node持有的线程,表示等待锁的线程
        volatile Thread thread;
		//表示下一个等待condition的Node
        Node nextWaiter;
    
    
    	//三个构造器
    	Node() {   
        }

        Node(Thread thread, Node mode) {    
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) {
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
}

AQS中有的变量

	//FIFO队列中的头Node
	private transient volatile Node head;

 	//FIFO队列中的尾Node
    private transient volatile Node tail;

    //同步状态,0表示未锁
    private volatile int state;

AQS是典型的模板模式的应用,FIFO队列的各种操作在AQS中已经实现,AQS的子类一般只需要重写tryAcquire(int arg)和tryRelease(int arg)两个方法即可。

三、ReentrantLock的实现

ReentrantLock根据传入构造方法的布尔型参数实例化出Sync的实现类FairSync和NonfairSync,分别表示公平的Sync和非公平的Sync。ReentrantLock使用较多的为是非公平锁,因为非公平锁吞吐量大

假设线程1调用了ReentrantLock的lock()方法,那么线程1将会独占锁:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbKKSwcH-1615132081190)(C:\Users\Shinelon\Desktop\abc.png)]

第一个获取锁的线程就做了两件事情:

1、设置AbstractQueuedSynchronizer的state为1

2、设置AbstractOwnableSynchronizer的thread为当前线程

这两步做完之后就表示线程1独占了锁。然后线程2也要尝试获取同一个锁,在线程1没有释放锁的情况下,线程2会阻塞。

线程2的方法调用链:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqMNJPPp-1615132081191)(C:\Users\Shinelon\Desktop\aa9.png)]

  final void lock() {
  //首先线程2尝试利用CAS去判断state是不是0,是0就设置为1,当然这一步操作肯定是失败的,因为线程1已经将state设置成了1,因此线程2一定会执行acquire方法:
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
 }
public final void acquire(int arg) {
    //第一个判断条件尝试获取一次锁,如果获取的结果为false,才会走第二个判断条件添加FIFO等待队列
    if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
 final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
 	//由于state是volatile的,所以state对线程2具有可见性,线程2拿到最新的state
    int c = getState();
    //再次判断一下能否持有锁(可能线程1同步代码执行得比较快,这会儿已经释放了锁)
    if (c == 0) {
         if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
         }
    } //判断当前线程和持有锁的线程是否相同
     //让某个线程可以多次调用同一个ReentrantLock,每调用一次给state+1,由于某个线程已经持有了锁,所以这里不会有竞争,因此不需要利用CAS设置state(相当于一个偏向锁*)。从这段代码可以看到,nextc每次加1,当nextc<0的时候抛出error,那么同一个锁最多能重入Integer.MAX_VALUE次
     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;
 }

AQS的addWaiter方法,添加等待队列:

private Node addWaiter(Node mode) {
    //创建一个当前线程的Node,模式为独占模式(因为传入的mode是一个NULL)
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    //再判断一下队列上有没有节点,没有就创建一个队列走enq方法
    if (pred != null) {
        //多线程并发有能会走这里
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
private Node enq(final Node node) {
    //死循环
    for (;;) {
         Node t = tail;
        //尾部的node部位空,则等待队列不为空,线程2为第一个需要添加到等待队列的,因为多线程并发,所以等待队列有可能不为空
         if (t == null) { 
     //由于线程2所在的Node是第一个要等待的Node,因此FIFO队列上肯定没有内容,tail为null
             //新建一个node对象
             Node h = new Node(); 
             //将下一个等待对象设置为线程2
             h.next = node;
             //将线程2设置为h的前一个等待对象,形成了环行链
             node.prev = h; 
             if (compareAndSetHead(h)) {
                 tail = node;
                 return h;
             }
         }
        else {

            node.prev = t;
            if (compareAndSetTail(t, node)) {
                //则将node添加到等待队列末尾
                t.next = node;
                 return t;
             }
         }
     }
 }

形成一个队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3FPoZrx-1615132081193)(C:\Users\Shinelon\Desktop\as7.png)]

上面全部走完后,会回到acquireQueued方法

 final boolean acquireQueued(final Node node, int arg) {
    try {
        boolean interrupted = false;
     		//死循环:
         for (;;) {
              final Node p = node.predecessor();
     			//再次判断一下线程2能不能获取锁(可能这段时间内线程1已经执行完了把锁释放了,state变为了0)
              if (p == head && tryAcquire(arg)) {
                  setHead(node);
                  p.next = null; // help GC
                  return interrupted;
             }
     			//不能,则调用AQS的shouldParkAfterFailedAcquire(p, node)方法,第一次会得到false,继续循环,第二次才会走第二个判断条件
             if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
                 interrupted = true;
         }
     } catch (RuntimeException ex) {
         cancelAcquire(node);
         throw ex;
     }
}
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     //第一次这个waitStatus是h的waitStatus,很明显是0,第二次为-1,会返回true
     int s = pred.waitStatus;
     if (s < 0)
         return true;
     if (s > 0) {  
     do {
     node.prev = pred = pred.prev;
     }
      while (pred.waitStatus > 0);
     pred.next = node;
     }
     else
         //把h的waitStatus设置为Noed.SIGNAL即-1并返回false
         compareAndSetWaitStatus(pred, 0, Node.SIGNAL);
     return false;
}

parkAndCheckInterrupt:

private final boolean parkAndCheckInterrupt() {
    //阻塞住了当前的线程
    LockSupport.park(this);
    return Thread.interrupted();
}
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    unsafe.park(false, 0L);
    setBlocker(t, null);
}

lock()的操作明了之后,就要探究一下unlock()的时候代码

unlock()的时候做了什么

调用ReentrantLock的unlock方法:

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

会调用AQS的release:

 public final boolean release(int arg) {
     if (tryRelease(arg)) {
         //能执行到这里说明state 为0
         Node h = head;
         //h不为null成立,h的waitStatus为-1,不等于0也成立,执行unparkSuccessor方法:
         if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
         return true;
     }
     return false;
 }

先调用Sync的tryRelease尝试释放锁:

 protected final boolean tryRelease(int releases) {
     //每次执行该方法,state都会减1
     int c = getState() - releases;
     //判断当前线程和持有锁的线程是否相等,不相等抛异常
     if (Thread.currentThread() != getExclusiveOwnerThread())
         throw new IllegalMonitorStateException();
     boolean free = false;
     //只有当c==0的时候才会让free=true,这和上面一个线程多次调用lock方法累加state是对应的,调用了多少次的lock()方法自然必须调用同样次数的unlock()方法才行,这样才把一个锁给全部解开
     if (c == 0) {
        free = true;
         //设置占有锁的线程为空
        setExclusiveOwnerThread(null);
     }
     setState(c);
     return free;
 }
 private void unparkSuccessor(Node node) {
		//下一个Node,也就是线程2
     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;
    }
     //等待队列不为空,执行unPark
     if (s != null)
         LockSupport.unpark(s.thread);
}

LockSupport的静态方法:

    public static void unpark(Thread thread) {
        if (thread != null)
            //jvm进行实现
            UNSAFE.unpark(thread);
    }

锁被解了怎样保证整个FIFO队列减少一个Node,回到了AQS的acquireQueued方法了

 final boolean acquireQueued(final Node node, int arg) {
     try {
        boolean interrupted = false;
        for (;;) {
             final Node p = node.predecessor();
             if (p == head && tryAcquire(arg)) {
                 setHead(node);
                 p.next = null; // help GC
                 return interrupted;
             }
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt())
                 interrupted = true;
         }
     } catch (RuntimeException ex) {
         cancelAcquire(node);
         throw ex;
     }
 }

被阻塞的线程2是被阻塞了,但是并没有return语句,所以,阻塞完成线程2依然会进行for循环。然后,阻塞完成了,线程2所在的Node的前驱Node是p,线程2尝试tryAcquire,成功,然后线程2就成为了head节点了,把p的next设置为null,这样原头Node里面的所有对象都不指向任何块内存空间,h属于栈内存的内容,方法结束被自动回收,这样随着方法的调用完毕,原头Node也没有任何的引用指向它了,这样它就被GC自动回收了。此时,遇到一个return语句,acquireQueued方法结束

setHead方法:

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

setHead方法里面的前驱Node是Null,也没有线程,那么为什么不用一个在等待的线程作为Head Node呢?

因为一个线程随时有可能因为中断而取消,而取消的话,Node自然就要被GC了,那GC前必然要把头Node的后继Node变为一个新的头而且要应对多种情况,这样就很麻烦。用一个没有thread的Node作为头,相当于起了一个引导作用,因为head没有线程,自然也不会被取消。

从尾到头遍历,找出离head最近的一个node,对这个node进行unPark操作。

ReentrantLock其他方法的实现

如果能理解ReentrantLock的实现方式

//获取ReentrantLock的lock()方法被调用了几次,就是state的当前值
final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}
//获取当前占有锁的线程,就是AbstractOwnableSynchronizer中exclusiveOwnerThread的值
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}
//从尾到头遍历一下,添加进ArrayList(等待队列)中
public final Collection<Thread> getQueuedThreads() {
    ArrayList<Thread> list = new ArrayList<Thread>();
    for (Node p = tail; p != null; p = p.prev) {
        Thread t = p.thread;
        if (t != null)
            list.add(t);
    }
    return list;
}
//从尾到头遍历一下,累加n,这两个方法可能是不准确的,因为遍历的时候可能别的线程又往队列尾部添加了Node。
public final int getQueueLength() {
    int n = 0;
    for (Node p = tail; p != null; p = p.prev) {
        if (p.thread != null)
            ++n;
    }
    return n;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值