前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ReentranLock及源码解析(学思想,一步一步点进源码)

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

作者头像
向着百万年薪努力的小赵
发布2022-12-02 10:40:15
2110
发布2022-12-02 10:40:15
举报
文章被收录于专栏:小赵的Java学习

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

文章目录

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之后差不多了

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 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.5执行acquireQueued方法
            • 3.3unlock解锁
              • 3.3.1tryRelease 方法
              • 3.3.2unparkSuccessor方法
              • 3.3.3为什么要从后往前?
            • 3.4唤醒head的下一个节点
            • 公平锁与非公平锁区别
            • 什么是AQS?
            • Lock与Synchronized的区别
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档