ReentranLock及源码解析(学思想,一步一步点进源码)

2022-12-02 10:40:15 浏览数 (1)

文章内容引用自 咕泡科技 咕泡出品,必属精品

文章目录

  • 1ReentrantLock解释
    • 1.1可重入栗子:
    • 1.2看到了栗子,让我们带着问题一起去探究它的原理吧
  • 2看答案之前想一下,如果让我们自己实现一把锁,你会怎么做?
  • 3ReentrantLock 源码解析
    • 3.01一张对源码解释的大图,思路清晰
    • 3.1构造函数
    • 3.2lock方法
      • 3.2.1compareAndsetstate
      • 3.2.2acquire
      • 3.2.3所谓的重入nonfairTryAcquire
      • 3.2.4等待队列acquireQueued前的准备
        • 3.2.4.1形成一个双向链表addWaiter
        • 3.2.4.2自旋
        • 3.2.4.3如果再来一个线程抢锁
      • 3.2.5执行acquireQueued方法
        • 3.2.5.1shouldParkAfterFailedAcquire方法
        • 3.2.5.2parkAndCheckInterrupt方法
    • 3.3unlock解锁
      • 3.3.1tryRelease 方法
      • 3.3.2unparkSuccessor方法
      • 3.3.3为什么要从后往前?
    • 3.4唤醒head的下一个节点
  • 公平锁与非公平锁区别
  • 什么是AQS?
  • Lock与Synchronized的区别

1ReentrantLock解释

我们知道在并发的场景下,如果同时对共享代码块进行访问时,会导致原子性、有序性、可见性问题。从而导致我业务出错。所以有时候,我们有些代码块不能进行并行,就得去改成串行!!之前我们已经知道了一个Synchronized重量级锁。该锁底层是JVM里面,通过monitor锁来实现串行,其他线程进行等待。

那么我们今天要讲的,也是一个锁,只不过相关实现是在java层面去做的。就是ReentrantLock可重入锁。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

1.1可重入栗子:

代码语言:javascript复制
public class ReentrantLockTest {
 
    public static void main(String[] args) throws InterruptedException {
 
        ReentrantLock lock = new ReentrantLock();
 
        for (int i = 1; i <= 3; i  ) {
            lock.lock();
        }
 
        for(int i=1;i<=3;i  ){
            try {
 
            } finally {
                lock.unlock();
            }
        }
    }
}

上面的代码通过lock()方法先获取锁三次,然后通过unlock()方法释放锁3次,程序可以正常退出。从上面的例子可以看出,ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。在加上ReentrantLock的的独占性,我们可以得出以下ReentrantLock和synchronized的相同点。

1.ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

上面的栗子过于简单,不够香甜,再来一个:

代码语言:javascript复制
public class ReentrantLockTest { 
	int i=0; 
	ReentrantLock reentrantLock=new ReentrantLock(); 
	public void incr() { 
		reentrantLock.lock(); //加上ReentrantLock
		i =10; 
		System.out.println(i); 
		reentrantLock.unlock(); //撤掉ReentrantLock,想一下为啥要在代码层面撤掉
	}
	public static void main(String[] args) throws InterruptedException { 
		ReentrantLockTest test=new ReentrantLockTest();
		Thread[] threads=new Thread[5]; 
		for (int j = 0; j < 5; j  ) { 
			threads[j] =new Thread(() -> { 
				test.incr(); 
			}); 
			threads[j].start(); 
		}
		for (int j = 0; j < 5; j  ) { 
			threads[j].join(); 
		} 
	} 
}

在每个线程执行incr方法时,都会先去reentrantLock执行lock方法拿锁。 那么执行结果就是线程安全的!

可是这样,每次只能有1个线程能执行incr方法,其他线程都是等待的!! 如果等待的线程太多,那么也会有问题,所以也可以不让线程等待,失败就失败,比如以下代码多线程执行,那么只会有1个执行成功,其他的线程拿不到锁

代码语言:javascript复制
ReentrantLock reentrantLock=new ReentrantLock(); 
	public void incr() { 
		if (reentrantLock.tryLock()) { 
			i  = 10; 
			System.out.println(i); 
			reentrantLock.unlock(); 
		} 
	}

1.2看到了栗子,让我们带着问题一起去探究它的原理吧

ReentrantLock 相关面试问题: 【1】什么是可重入,什么是可重入锁? 它用来解决什么问题? 【2】ReentrantLock的核心是 AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。 【3】ReentrantLock 是如何实现公平锁的? 【4】ReentrantLock 是如何实现非公平锁的? 【5】ReentrantLock 默认实现的是公平还是非公平锁? 【6】使用ReentrantLock 实现公平和非公平锁的示例? 【7】ReentrantLock 和 Synchronized的对比?

为什么叫可重入?核心是什么,原理是怎么样的? 在我们直接看答案之前,不妨让我们想一下:

2看答案之前想一下,如果让我们自己实现一把锁,你会怎么做?

需求很简单,我的代码块只能同时有1个线程来执行,当存在并发时,也就是如果有多个线程来抢占的话,去排队或者处理失败。

第一,只能有1个线程来执行,那么我们就得有个标记,来标记这个任务是否有线程在执行,并且这个标记是不能有并发的。 比如我们之前的synchronized锁,那么这个标记就是在对象头,如果是不同的对象,那么就会失效。 那这个标记有什么要求呢?

  1. 这个标记是不是需要线程可见
  2. 获取这个标记必须要线程安全的,如果多个线程都能并行的得到标记,那也就失去了标记的意义。

还有我会记录当前之前这个代码的线程是哪个。 拿不到锁的线程呢?要么等待(用一些数据结构进行保存)要么就处理失败,所以:

第二.能抢到的线程的正常执行代码块,如果抢不到,那么要么排队,要么就不管。 就好比你在动车上。厕所就一个,但是可能有很多人肚子痛,那么肯定是第一个抢到的人用厕所,其他的人排队。或者不上了

一切锁的实现思想大概都是这个,本质上都是一样的,只是实现方式不一样

3ReentrantLock 源码解析

3.01一张对源码解释的大图,思路清晰

有同学反馈看着比较困难。。。 emmmmm 连夜画了一张图,标出了代码的大致逻辑,下面的源码看不太懂的可以参照着这张图看

这张图我画了一下午,给个赞点点关注吧 ReentrantLock 的使用,无非就是lock和unlock和别的代码配合着使用,从lock开始看,看之前,按照我看源码的习惯,我会先去看一下构造函数,看一下成员变量,方便我们去理解和查阅源码

3.1构造函数

代码语言:javascript复制
public ReentrantLock() { 
	sync = new NonfairSync(); 
}

好嘛,构造就一行,new了一个非公平锁,往下翻找lock函数,我们发现Lock会有2个类实现,一个是FairSync 一个是NonfairSync。这2个的区别我们后面会讲,其实就是一个是公平锁,一个是非公平锁。

在创建ReentrantLock对象的时候,默认是非公平锁,所以,我们先看非公平锁逻辑

3.2lock方法

代码语言:javascript复制
final void lock( {
	//cas 比较并替换,修改AbstractQueuedsynchronizer类的state字段,用vo1atile修饰,并且jvm底层cas会有锁操作,只能有1个线程能更改成功
	if (compareAndsetstate(0,1))//如果从0改成1 说明能抢占到锁
	//修改Abstractownab1esynchronizer的exclusiveownerThread为当前线程
		setExclusiveownerThread(Thread. currentThread());
	else
		//线程抢占不到的情况执行
		acquire(1);
}

3.2.1compareAndsetstate

里面的compareAndsetstate就是我们经常见也经常听说的CAS了 总结一下,该方法,如果能抢占到锁,做了2件事情

  1. 将AbstractQueuedSynchronizer类的state字段从0改成1 ,
  2. AbstractOwnableSynchronizer的exclusiveOwnerThread为当前线程 exclusiveOwnerThread是AbstractOwnableSynchronizer类里面的一个字段

state字段作为是否拿到锁的标记

如果拿不到呢,第一个线程拿到锁了但是还没有释放,这时另一个线程过来说我也想要这个锁:

代码语言:javascript复制
        final void lock() {
        //因为被线程1已经更改为1了, 所以执行失败
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //进入acquire
                acquire(1);
        }

3.2.2acquire

看看acquire

代码语言:javascript复制
    public final void acquire(int arg) {
    //尝试去加锁,看锁是不是释放了,该方法也有2个实现,FairSync与 NonfairSync,我们暂时只看默认的NonfairSync 
    //如果tryAcquire为false,则执行acquireQueued,并且 acquireQueued为true,执行selfInterrupt
        if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

看一下作为判断条件的tryAcquire方法:

代码语言:javascript复制
    protected boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

呵,往里点吧 代码逻辑都写在注释里面了:所谓的重入 就在这了

3.2.3所谓的重入nonfairTryAcquire

代码语言:javascript复制
        final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程,现在我们线程2来抢占锁,并且线程1没有执行完,所以 当前是线程2
            final Thread current = Thread.currentThread();
            //获取AbstractQueuedSynchronizer的值,因为线程1把他设为了 1,所以c为1
            int c = getState();
            //c为0,是线程1释放了锁的场景,但是现在没有,不满足
            if (c == 0) {
            //如果满足,尝试着去抢占锁(把state改成1)
                if (compareAndSetState(0, acquires)) {
                //设置当前执行代码块的线程为线程2
                    setExclusiveOwnerThread(current);
                    //返回ture,代表线程2拿到了锁,去正常执行业务代码
                    return true;
                }
            }
            //如果c!=0.说明有线程在占有锁,那么如果占有的线程跟当前的线程一 致,说明是同一个线程抢占多次锁
            else if (current == getExclusiveOwnerThread()) {
            //将state 改成state  1 这个时候,state不止是一个状态,而 是代表加锁了多少次(重入次数)
                int nextc = c   acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //如果抢占不到锁,并且当前线程不是占有锁线程,返回false
            return false;
        }

所以,返回了个false,执行acquireQueued方法:

3.2.4等待队列acquireQueued前的准备

执行acquireQueued,里面先执行addWaiter方法,该方法的主要作用是去形成一个双向链表

3.2.4.1形成一个双向链表addWaiter
代码语言:javascript复制
//参数mode 为null(在lock没有用处 condition中有用)
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //new node对象,对象为AQS中的一个数据结构,里面存有线程信息,以及前 后节点数据、首尾节点等信息
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;//tail node里面定义尾结点,默认为null 
        //所以,thread2进来的时候,pred为null,不满足下面条件
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //进入enq方法
        enq(node);
        //返回node节点
        return node;
    }

瞅一眼enq方法

3.2.4.2自旋
代码语言:javascript复制
//传入的是new的那个node
    private Node enq(final Node node) {
    //自旋
        for (;;) {
        //tail默认为null
            Node t = tail;
            //第一次满足条件
            if (t == null) { // Must initialize
            //cas去设置head节点
                if (compareAndSetHead(new Node()))
                //设置成功,将tail设置为head节点,继续自旋
                    tail = head;
            } else {
            //第二次自旋,当t!=null时进入
            //node的前节点指向t
                node.prev = t;
                //cas将tail节点设置为node
                if (compareAndSetTail(t, node)) {
                //设置t的next为node
                    t.next = node;
                    //跳出循环,返回t
                    return t;
                }
            }
        }
    }

现在,第二个线程抢不到锁的结果已经明示了,建了一个等待队列:

3.2.4.3如果再来一个线程抢锁

假如thread3也来抢占锁,这个时候锁还没释放。还是会进入addWaiter方法:

代码语言:javascript复制
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //new一个thread3的node
        // Try the fast path of enq; backup to full enq on failure
        //tail现在指向的是thread2,所以pred为thread2的node
        Node pred = tail;
        //不为null,满足条件
        if (pred != null) {
        //thread3的node指向thread2的node
            node.prev = pred;
            //将tail节点的指向改成thread3的node
            if (compareAndSetTail(pred, node)) {
            //thread2的next指向thread3的节点
                pred.next = node;
                //返回thread3的node
                return node;
            }
        }
        enq(node);
        return node;
    }

经常刷算法的看到这里肯定感到异常的熟悉,这不就是往双向链表后面插节点嘛

thread3抢占不到锁的时候,我们的node列表变成了如下图

那么有多少线程抢占锁,我的双向列表就会有多少个等待节点。 现在,什么是重入,以及等待的列表、ReentrantLock内部结构、基于哪种锁实现,是不是随着代码的跟进一步一步都清楚了?

终于正式执行acquireQueued方法了:

3.2.5执行acquireQueued方法

该方法主要是根据node节点去操作相关获取锁以及park操作

代码语言:javascript复制
//node为当前线程的node节点
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            //获取node节点的前一个节点
                final Node p = node.predecessor();
                //如果p==head 如果是thread2,那么满足,但是thread2 tryAcquire失败,因为thread1占有锁;所以不满足
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //进入shouldParkAfterFailedAcquire与 parkAndCheckInterrupt
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
3.2.5.1shouldParkAfterFailedAcquire方法

瞅一眼shouldParkAfterFailedAcquire方法: 线程状态大家还不知道吧,这里贴出来 线程的状态标识 变量 waitStatus 则表示当前 Node 结点的等待状态,共有5种取值 CANCELLEDSIGNALCONDITIONPROPAGATE0

  • CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
  • SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
  • CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从条件队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
  • 0:新结点入队时的默认状态。

注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。

这个方法的官方注释一大堆,我再解释一下

代码语言:javascript复制
//pred为当前线程的前节点 node为当前线程节点
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//得到节点的状态,默认为0 第二 次进入为-1
        if (ws == Node.SIGNAL)//waitStatus!=-1不满足 第二次 满足条件,返回true
            /*
             * 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 {//进入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.
             */
             //修改当前节点的前一个节点的状态为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //返回false,进入外面的自旋,
        return false;
    }

重新进入自旋,进入shouldParkAfterFailedAcquire方法,判断ws=-1,满足条件 走到这里,流程图如下:

这时如果thread3进来尝试获取锁

瞅一眼parkAndCheckInterrupt方法:

3.2.5.2parkAndCheckInterrupt方法
代码语言:javascript复制
private final boolean parkAndCheckInterrupt() { 
	LockSupport.park(this); //如果没有拿到锁,线程park  waiting状态 唤醒有2个场景(1第一个线程释放锁唤醒 2interrupt优雅中断) 
	return Thread.interrupted(); //获取中断状态并且复位 
}

有不知道interrupt优雅中断什么意思的小伙伴可以看一下我的另一篇博客: 链接: 怎么查看线程的状态及interrupt优雅的关闭线程和interrupt()、interrupted()、isInterrupted()的作用以及区别在哪?

至此,Lock的代码就走完了,线程一拿着锁,线程二进来,建了等待队列的头,把自己放在末尾然后调用park阻塞自己,等待线程一释放锁,线程三如果进来,将自己放在等待队列后面阻塞自己········

现在,假如thread1业务代码已经执行完,调用unlock

3.3unlock解锁

代码语言:javascript复制
public void unlock() { 
	sync.release(1); 
}

点进release:

代码语言:javascript复制
    public final boolean release(int arg) {
    //调用tryRelease方法 尝试释放锁
        if (tryRelease(arg)) {
        //如果锁释放成功
            Node h = head;
            //得到head节点,如果head节点!=null 并且状态!=0 满足条件
            if (h != null && h.waitStatus != 0)
            //执行unparkSuccessor
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease 方法 去更改state状态

3.3.1tryRelease 方法

代码语言:javascript复制
        protected final boolean tryRelease(int releases) {
        //假如thread1只重入了一次。c-1=0
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
            //判断加锁的线程是不是当前需要释放的线程
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果thread1只加锁一次
            if (c == 0) {
                free = true;
                //将当前拿锁线程设置为空
                setExclusiveOwnerThread(null);
            }
            //将state设置为0
            setState(c);
            return free;
        }

tryRelease 执行完毕后,state=0 ,exclusiveOwnerThread=null。这个时候,其他线程可以抢占锁了。 执行unparkSuccessor:

3.3.2unparkSuccessor方法

代码语言:javascript复制
//node 为head节点
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
         //得到head的节点状态
        int ws = node.waitStatus;
        if (ws < 0)//现在的状态为-1 满足条件
            compareAndSetWaitStatus(node, ws, 0);//将头节点改成 0

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
         //得到head节点的下一个节点,我们的场景为thread2的
        Node s = node.next;
        //thread2node的状态为-1,正常状态
        if (s == null || s.waitStatus > 0) {
            s = null;
            //如果需要释放锁的那个线程是取消状态,从后往前找到最前面的那 一个node
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //满足条件
        if (s != null)
        //唤醒thread2线程
            LockSupport.unpark(s.thread);
    }

如果获取锁的线程node被取消或者异常,那么从后往前找到异常节点后的第一个正常node

3.3.3为什么要从后往前?

因为有可能出现前指针没有的场景,在enq存在并发的时候(就是前面那个enq):

代码语言:javascript复制
    private Node enq(final Node node) {
    //自旋
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            //node的前节点指向t
                node.prev = t;
                if (compareAndSetTail(t, node)) {
					//以下逻辑再其他现在执行完之后执行就会出现如下图场 景,所以必须从后往前
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.4唤醒head的下一个节点

记得刚才3.2.5.2的parkAndCheckInterrupt方法嘛 进入到阻塞的地方parkAndCheckInterrupt方法

代码语言:javascript复制
private final boolean parkAndCheckInterrupt() { 
	LockSupport.park(this); //被释放锁唤醒,如果外面中断过范围false,否则返回true,并复位 
	return Thread.interrupted(); 
}

进入acquireQueued方法的自旋

代码语言:javascript复制
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            //获取thread2 node的前节点
                final Node p = node.predecessor();
                //thread2的前节点是head,并且这个时候thread1已经释放 锁,能拿到锁 if
                if (p == head && tryAcquire(arg)) {
                //将thread2的节点设置为首节点,并且把thread2节点 的thread、pre设置为null
                    setHead(node);
                    //这个时候之前的node节点还存在强引用,将next关联 去除,方便回收之前的head节点
                    p.next = null; // help GC
                    failed = false;
                    //返回
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

走完后得到图如下:

至此,加锁与释放锁都看完了,前面提出的问题你是否得到了答案呢? 学过算法的小伙伴应该看起来不吃力,因为这里面的代码其实就是我们刷算法题的解题思路嘛,这也是大厂为什么要求一定要考察算法的一个原因吧,思想真的很重要 没刷过算法的同学们,你们看起来不吃力的话,那说明有天赋啊,赶紧刷刷算法题,冲大厂!!

公平锁与非公平锁区别

我们Lock有2个类的实现,一个是FairSync,一个是NonfairSync。FairSync是公平锁 NonfairSync是非公平锁。

公平,体现在我所有的线程,都必须按照排队的顺序来,不能进行插队。

什么时候可以插队? 当我释放锁的时候,会先去更改state以及占有锁的线程。这个时候所有的线程都能去抢占锁,如果有不是在队列里的线程来抢占锁,也是能抢到锁的。

所以,如果我限制,当我释放锁的时候,必须是等待队里的线程才能获取锁则是公平锁,如果没有限制,则是非公平锁。

限制代码,在公平锁获取锁的时候,判断源码如下 : FairSync尝试加锁的代码

代码语言:javascript复制
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) { 
		//获取锁加了条件hasQueuedPredecessors 
		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;
}

hasQueuedPredecessors方法: 如果返回true,不允许抢占 false是允许抢占

代码语言:javascript复制
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. 
	//先获取tail,防止tail不为空 head为空的情况 但是head为空, tail为空或者都为空没事 
	//保证不会h为空还进入h.next判断 
	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()); 
}
  1. 如果h!=t 说明 有线程在等待 或者正在抢占(head不为null。tail为null 的情况) 这个时候执行
代码语言:javascript复制
((s = h.next) == null || s.thread != Thread.currentThread());

反之,没有线程在等待,为false,允许抢占锁

  1. 假如有线程在等待,则 ((s = h.next) == null || s.thread != Thread.currentThread())满足一个条件就不允许抢占锁 都为false就允许抢 占

(s = h.next) == null head的下一个节点为空 代表有线程进入enq队列,但 是还没执行完,这个时候,其他线程不可抢占

s.thread != Thread.currentThread() 代表头节点的下一个节点不是我当前 抢占锁的线程!不可抢占

总之。该方法就是保证,我抢占到锁的线程,如果等待队列有数据或者正在进数据,那必须只能是head的下一个节点得到锁

什么是AQS?

AQS是juc包下的一个工具类,全称是AbstractQueuedSynchronizer 抽象队列同步器。我们并发编程中的很多类的功能底层都是基于AQS来实现。比如ReentrantLock 、CountDownLatch 、condition等等。

首先数据结构。既然是实现并发的核心类,那么AQS中维护了一个state字段,代表锁的抢占情况。并提供对state的cas操作。以及提供加锁的模板方法,比如tryAcquire,自己可以去重现实现相关逻辑。

同时,抢不到的线程需要排队并且等待,所以AQS中有个线程等待队列。它里面最主要的是有一个双向列表。 节点的数据结构是node node存有线程的信息,以及node的状态。同时提供对双向列表的相关操作方法。

如果线程抢占不到锁,就会进入AQS中的等待队列,并且park。

同时提供了释放锁都相关方法,释放锁会唤醒相关线程。进行锁抢占操作。

Lock与Synchronized的区别

1.synchronized底层jvm层面实现 ReentrantLock 为java的juc类 2.synchronized异常会释放锁 lock必须手动释放 3.synchronized不能响应中断,lock可以响应中断 4.synchronized可重入,非公平 ; lock可重入,可公平与非公平 5.Lock提高读写能力,在1.6之前性能比synchronized高,但是1.6之后差不多了

0 人点赞