JUC AQS ReentrantLock源码分析(一)

并发编程(七)——JUC并发工具 CountDownLatch就是JUC包下的一个工具,整个工具最核心的功能就是计数器。如果有三个业务需要并行处理,并且需要知道三个业务全部都处理完毕了。需要一个并发安全的计数器来操作。CountDownLatch就可以实现。给CountDownLatch设置一个数值。可以设置3。每个业务处理完毕之后,执行一次countDown方法,指定的3每次在执行countDown方法时,对3进行-1。主线程可以在业务处理时,执行await,主线程会阻塞等待任务处理完毕。 阅读详情
AI摘要

本文深入探讨了Java中ReentrantLock的实现原理及其与AbstractQueuedSynchronizer(AQS)的关系,分析了自旋锁、yield、sleep及park在同步机制中的作用,详细解释了AQS队列的工作流程,包括节点设计、上锁过程和tryAcquire方法的源码分析。

正文之前说一下我在优快云有一门收费课程,需要的可以点击购买
https://edu.youkuaiyun.com/course/detail/32499

Java的内置锁一直都是备受争议的,在JDK
1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还是存在一些缺陷的:虽然synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式在高并发场景下性能大打折扣。

如何自己来实现一个同步

自旋实现一个同步

volatile int status=0;//标识---是否有线程在同步块-----是否有线程上锁成功
void lock(){
	while(!compareAndSet(0,1)){
	}
	//lock
}
void unlock(){
	status=0;
}
boolean compareAndSet(int except,int newValue){
	//cas操作,修改status成功则返回true
}

缺点:耗费cpu资源。没有竞争到锁的线程会一直占用cpu资源进行cas操作,假如一个线程获得锁后要花费Ns处理业务逻辑,那另外一个线程就会白白的花费Ns的cpu资源
解决思路:让得不到锁的线程让出CPU

yield+自旋实现同步

volatile int status=0;
void lock(){
	while(!compareAndSet(0,1)){
     yield();//自己实现
	}
	//lock

}
void unlock(){
	status=0;
}

要解决自旋锁的性能问题必须让竞争锁失败的线程不空转,而是在获取不到锁的时候能把cpu资源给让出来,yield()方法就能让出cpu资源,当线程竞争锁失败时,会调用yield方法让出cpu。
自旋+yield的方式并没有完全解决问题,当系统只有两个线程竞争锁时,yield是有效的。需要注意的是该方法只是当前让出cpu,有可能操作系统下次还是选择运行该线程,比如里面有2000个线程,想想会有什么问题?

sleep+自旋方式实现同步

volatile int status=0;
void lock(){
	while(!compareAndSet(0,1)){
		sleep(10);
	}
	//lock

}
void unlock(){
	status=0;
}

缺点:sleep的时间为什么是10?怎么控制呢?很多时候就算你是调用者本身其实你也不知道这个时间是多少

park+自旋方式实现同步

volatile int status=0;
Queue parkQueue;//集合 数组  list

void lock(){
	while(!compareAndSet(0,1)){
		//
		park();
	}
	//lock    10分钟
   。。。。。。
   unlock()
}

void unlock(){
	lock_notify();
}

void park(){
	//将当期线程加入到等待队列
	parkQueue.add(currentThread);
	//将当期线程释放cpu  阻塞
	releaseCpu();
}
void lock_notify(){
	//得到要唤醒的线程头部线程
	Thread t=parkQueue.header();
	//唤醒等待线程
	unpark(t);
}

这种方法就比较完美,当然我写的都伪代码,我看看大师是如何利用这种机制来实现同步的;JDK的JUC包下面ReentrantLock类的原理就是利用了这种机制;

ReentrantLock源码分析之上锁过程

AQS(AbstractQueuedSynchronizer)类的设计主要代码(具体参考源码)

private transient volatile Node head; //队首
private transient volatile Node tail;//尾
private volatile int state;//锁状态,加锁成功则为1,重入+1 解锁则为0

AQS当中的队列示意图
在这里插入图片描述

Node类的设计

public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
}

上锁过程重点

锁对象:其实就是ReentrantLock的实例对象,下文应用代码第一行中的lock对象就是所谓的锁
自由状态:自由状态表示锁对象没有被别的线程持有,计数器为0
计数器:再lock对象中有一个字段state用来记录上锁次数,比如lock对象是自由状态则state为0,如果大于零则表示被线程持有了,当然也有重入那么state则>1
waitStatus:仅仅是一个状态而已;ws是一个过渡状态,在不同方法里面判断ws的状态做不同的处理,所以ws=0有其存在的必要性
tail:队列的队尾 head:队列的对首 ts:第二个给lock加锁的线程 tf:第一个给lock加锁的线程 tc:当前给线程加锁的线程
tl:最后一个加锁的线程 tn:随便某个线程
当然这些线程有可能重复,比如第一次加锁的时候tf=tc=tl=tn
节点:就是上面的Node类的对象,里面封装了线程,所以某种意义上node就等于一个线程

首先一个简单的应用
 final ReentrantLock lock = new ReentrantLock(true);
 Thread t1= new Thread("t1"){
     @Override
     public void run() {
         lock.lock();
         logic();
         lock.unlock();
     }
 };
t1.start();
公平锁lock方法的源码分析
final void lock() {
    acquire(1);//1------标识加锁成功之后改变的值
}
非公平锁的looc方法
final void lock() {
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		 acquire(1);
} 

下面给出他们的代码执行逻辑的区别图

在这里插入图片描述

公平锁的上锁是必须判断自己是不是需要排队;而非公平锁是直接进行CAS修改计数器看能不能加锁成功;如果加锁不成功则乖乖排队(调用acquire);所以不管公平还是不公平;只要进到了AQS队列当中那么他就会排队;一朝排队;永远排队记住这点

acquire方法方法源码分析
public final void acquire(int arg) {
    //tryAcquire(arg)尝试加锁,如果加锁失败则会调用acquireQueued方法加入队列去排队,如果加锁成功则不会调用
    //acquireQueued方法下文会有解释
    //加入队列之后线程会立马park,等到解锁之后会被unpark,醒来之后判断自己是否被打断了;被打断下次分析
    //为什么需要执行这个方法?下文解释
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquire方法首先会调用tryAcquire方法,注意tryAcquire的结果做了取反

tryAcquire方法源码分析
protected final boolean tryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取lock对象的上锁状态,如果锁是自由状态则=0,如果被上锁则为1,大于1表示重入
    int c = getState();
    if (c == 0) {//没人占用锁--->我要去上锁----1、锁是自由状态
        //hasQueuedPredecessors,判断自己是否需要排队这个方法比较复杂,
        //下面我会单独介绍,如果不需要排队则进行cas尝试加锁,如果加锁成功则把当前线程设置为拥有锁的线程
        //继而返回true
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //设置当前线程为拥有锁的线程,方面后面判断是不是重入(只需把这个线程拿出来判断是否当前线程即可判断重入)    
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果C不等于0,而且当前线程不等于拥有锁的线程则不会进else if 直接返回false,加锁失败
    //如果C不等于0,但是当前线程等于拥有锁的线程则表示这是一次重入,那么直接把状态+1表示重入次数+1
    //那么这里也侧面说明了reentrantlock是可以重入的,因为如果是重入也返回true,也能lock成功
    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判断是否需要排队的源码分析

这里需要记住一点,整个方法如果最后返回false,则去加锁,如果返回true则不加锁,因为这个方法被取反了

public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    /**
     * 下面提到的所有不需要排队,并不是字面意义,我实在想不出什么词语来描述这个“不需要排队”;不需要排队有两种情况
     * 一:队列没有初始化,不需要排队,不需要排队,不需要排队;直接去加锁,但是可能会失败;为什么会失败呢?
     * 假设两个线程同时来lock,都看到队列没有初始化,都认为不需要排队,都去进行CAS修改计数器;有一个必然失败
     * 比如t1先拿到锁,那么另外一个t2则会CAS失败,这个时候t2就会去初始化队列,并排队
     *
     * 二:队列被初始化了,但是tc过来加锁,发觉队列当中第一个排队的就是自己;比如重入;
     * 那么什么叫做第一个排队的呢?下面解释了,很重要往下看;
     * 这个时候他也不需要排队,不需要排队,不需要排队;为什么不需要排对?
     * 因为队列当中第一个排队的线程他会去尝试获取一下锁,因为有可能这个时候持有锁锁的那个线程可能释放了锁;
     * 如果释放了就直接获取锁执行。但是如果没有释放他就会去排队,
     * 所以这里的不需要排队,不是真的不需要排队
     *
     * h != t 判断首不等于尾这里要分三种情况
     * 1、队列没有初始化,也就是第一个线程tf来加锁的时候那么这个时候队列没有初始化,
     * h和t都是null,那么这个时候判断不等于则不成立(false)那么由于是&&运算后面的就不会走了,
     * 直接返回false表示不需要排队,而前面又是取反(if (!hasQueuedPredecessors()),所以会直接去cas加锁。
     * ----------第一种情况总结:队列没有初始化没人排队,那么我直接不排队,直接上锁;合情合理、有理有据令人信服;
     * 好比你去火车站买票,服务员都闲的蛋疼,整个队列都没有形成;没人排队,你直接过去交钱拿票
     *
     * 2、队列被初始化了,后面会分析队列初始化的流程,如果队列被初始化那么h!=t则成立;(不绝对,还有第3中情况)
     * h != t 返回true;但是由于是&&运算,故而代码还需要进行后续的判断
     * (有人可能会疑问,比如队列初始化了;里面只有一个数据,那么头和尾都是同一个怎么会成立呢?
     * 其实这是第3种情况--对头等于对尾;但是这里先不考虑,我们假设现在队列里面有大于1个数据)
     * 大于1个数据则成立;继续判断把h.next赋值给s;s有是对头的下一个Node,
     * 这个时候s则表示他是队列当中参与排队的线程而且是排在最前面的;
     * 为什么是s最前面不是h嘛?诚然h是队列里面的第一个,但是不是排队的第一个;下文有详细解释
     * 因为h也就是对头对应的Node对象或者线程他是持有锁的,但是不参与排队;
     * 这个很好理解,比如你去买车票,你如果是第一个这个时候售票员已经在给你服务了,你不算排队,你后面的才算排队;
     * 队列里面的h是不参与排队的这点一定要明白;参考下面关于队列初始化的解释;
     * 因为h要么是虚拟出来的节点,要么是持有锁的节点;什么时候是虚拟的呢?什么时候是持有锁的节点呢?下文分析
     * 然后判断s是否等于空,其实就是判断队列里面是否只有一个数据;
     * 假设队列大于1个,那么肯定不成立(s==null---->false),因为大于一个Node的时候h.next肯定不为空;
     * 由于是||运算如果返回false,还要判断s.thread != Thread.currentThread();这里又分为两种情况
     *        2.1 s.thread != Thread.currentThread() 返回true,就是当前线程不等于在排队的第一个线程s;
     *              那么这个时候整体结果就是h!=t:true; (s==null false || s.thread != Thread.currentThread() true  最后true)
     *              结果: true && true 方法最终放回true,所以需要去排队
     *              其实这样符合情理,试想一下买火车票,队列不为空,有人在排队;
     *              而且第一个排队的人和现在来参与竞争的人不是同一个,那么你就乖乖去排队
     *        2.2 s.thread != Thread.currentThread() 返回false 表示当前来参与竞争锁的线程和第一个排队的线程是同一个线程
     *             这个时候整体结果就是h!=t---->true; (s==null false || s.thread != Thread.currentThread() false-----> 最后false)
     *            结果:true && false 方法最终放回false,所以不需要去排队
     *            不需要排队则调用 compareAndSetState(0, acquires) 去改变计数器尝试上锁;
     *            这里又分为两种情况(日了狗了这一行代码;有同学课后反应说子路老师老师老是说这个AQS难,
     *            你现在仔细看看这一行代码的意义,真的不简单的)
     *             2.2.1  第一种情况加锁成功?有人会问为什么会成功啊,如这个时候h也就是持有锁的那个线程执行完了
     *                      释放锁了,那么肯定成功啊;成功则执行 setExclusiveOwnerThread(current); 然后返回true 自己看代码
     *             2.2.2  第二种情况加锁失败?有人会问为什么会失败啊。假如这个时候h也就是持有锁的那个线程没执行完
     *                       没释放锁,那么肯定失败啊;失败则直接返回false,不会进else if(else if是相对于 if (c == 0)的)
     *                      那么如果失败怎么办呢?后面分析;
     *
     *----------第二种情况总结,如果队列被初始化了,而且至少有一个人在排队那么自己也去排队;但是有个插曲;
     * ----------他会去看看那个第一个排队的人是不是自己,如果是自己那么他就去尝试加锁;尝试看看锁有没有释放
     *----------也合情合理,好比你去买票,如果有人排队,那么你乖乖排队,但是你会去看第一个排队的人是不是你女朋友;
     *----------如果是你女朋友就相当于是你自己(这里实在想不出现实世界关于重入的例子,只能用男女朋友来替代);
     * --------- 你就叫你女朋友看看售票员有没有搞完,有没有轮到你女朋友,因为你女朋友是第一个排队的
     * 疑问:比如如果在在排队,那么他是park状态,如果是park状态,自己怎么还可能重入啊。
     * 希望有同学可以想出来为什么和我讨论一下,作为一个菜逼,希望有人教教我
     *  
     * 
     * 3、队列被初始化了,但是里面只有一个数据;什么情况下才会出现这种情况呢?ts加锁的时候里面就只有一个数据?
     * 其实不是,因为队列初始化的时候会虚拟一个h作为头结点,tc=ts作为第一个排队的节点;tf为持有锁的节点
     * 为什么这么做呢?因为AQS认为h永远是不排队的,假设你不虚拟节点出来那么ts就是h,
     *  而ts其实需要排队的,因为这个时候tf可能没有执行完,还持有着锁,ts得不到锁,故而他需要排队;
     * 那么为什么要虚拟为什么ts不直接排在tf之后呢,上面已经时说明白了,tf来上锁的时候队列都没有,他不进队列,
     * 故而ts无法排在tf之后,只能虚拟一个thread=null的节点出来(Node对象当中的thread为null);
     * 那么问题来了;究竟什么时候会出现队列当中只有一个数据呢?假设原队列里面有5个人在排队,当前面4个都执行完了
     * 轮到第五个线程得到锁的时候;他会把自己设置成为头部,而尾部又没有,故而队列当中只有一个h就是第五个
     * 至于为什么需要把自己设置成头部;其实已经解释了,因为这个时候五个线程已经不排队了,他拿到锁了;
     * 所以他不参与排队,故而需要设置成为h;即头部;所以这个时间内,队列当中只有一个节点
     * 关于加锁成功后把自己设置成为头部的源码,后面会解析到;继续第三种情况的代码分析
     * 记得这个时候队列已经初始化了,但是只有一个数据,并且这个数据所代表的线程是持有锁
     * h != t false 由于后面是&&运算,故而返回false可以不参与运算,整个方法返回false;不需要排队
     *
     *
     *-------------第三种情况总结:如果队列当中只有一个节点,而这种情况我们分析了,
     *-------------这个节点就是当前持有锁的那个节点,故而我不需要排队,进行cas;尝试加锁
     *-------------这是AQS的设计原理,他会判断你入队之前,队列里面有没有人排队;
     *-------------有没有人排队分两种情况;队列没有初始化,不需要排队
     *--------------队列初始化了,按时只有一个节点,也是没人排队,自己先也不排队
     *--------------只要认定自己不需要排队,则先尝试加锁;加锁失败之后再排队;
     *--------------再一次解释了不需要排队这个词的歧义性
     *-------------如果加锁失败了,在去park,下文有详细解释这样设计源码和原因
     *-------------如果持有锁的线程释放了锁,那么我能成功上锁
     *
     **/
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

到此我们已经解释完了!tryAcquire(arg)方法,为了方便我再次贴一下代码

public final void acquire(int arg) {
    //tryAcquire(arg)尝试加锁,如果加锁失败则会调用acquireQueued方法加入队列去排队,如果加锁成功则不会调用
    //acquireQueued方法下文会有解释
    //加入队列之后线程会立马park,等到解锁之后会被unpark,醒来之后判断自己是否被打断了
    //为什么需要执行这个方法?下次解释
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
acquireQueued(addWaiter(Node.exclusive),arg))方法解析

如果代码能执行到这里说tc需要排队
需要排队有两种情况—换言之代码能够执行到这里有两种情况:
1、tf持有了锁,并没有释放,所以tc来加锁的时候需要排队,但这个时候—队列并没有初始化
2、tn(无所谓哪个线程,反正就是一个线程)持有了锁,那么由于加锁tn!=tf(tf是属于第一种情况,我们现在不考虑tf了),所以队列是一定被初始化了的,tc来加锁,那么队列当中有人在排队,故而他也去排队

addWaiter(Node.EXCLUSIVE)源码分析
private Node addWaiter(Node mode) {
    //由于AQS队列当中的元素类型为Node,故而需要把当前线程tc封装成为一个Node对象,下文我们叫做nc
    Node node = new Node(Thread.currentThread(), mode);
    //tail为对尾,赋值给pred 
    Node pred = tail;
    //判断pred是否为空,其实就是判断对尾是否有节点,其实只要队列被初始化了对尾肯定不为空,
    //假设队列里面只有一个元素,那么对尾和对首都是这个元素
    //换言之就是判断队列有没有初始化
    //上面我们说过代码执行到这里有两种情况,1、队列没有初始化和2、队列已经初始化了
    //pred不等于空表示第二种情况,队列被初始化了,如果是第二种情况那比较简单
   //直接把当前线程封装的nc的上一个节点设置成为pred即原来的对尾
   //继而把pred的下一个节点设置为当nc,这个nc自己成为对尾了
    if (pred != null) {
        //直接把当前线程封装的nc的上一个节点设置成为pred即原来的对尾,对应 10行的注释
        node.prev = pred;
        //这里需要cas,因为防止多个线程加锁,确保nc入队的时候是原子操作
        if (compareAndSetTail(pred, node)) {
            //继而把pred的下一个节点设置为当nc,这个nc自己成为对尾了 对应第11行注释
            pred.next = node;
            //然后把nc返回出去,方法结束
            return node;
        }
    }
    //如果上面的if不成了就会执行到这里,表示第一种情况队列并没有初始化---下面解析这个方法
    enq(node);
    //返回nc
    return node;
}


private Node enq(final Node node) {//这里的node就是当前线程封装的node也就是nc
    //死循环
    for (;;) {
        //对尾复制给t,上面已经说过队列没有初始化,
        //故而第一次循环t==null(因为是死循环,因此强调第一次,后面可能还有第二次、第三次,每次t的情况肯定不同)
        Node t = tail;
        //第一次循环成了成立
        if (t == null) { // Must initialize
            //new Node就是实例化一个Node对象下文我们称为nn,
            //调用无参构造方法实例化出来的Node里面三个属性都为null,可以关联Node类的结构,
            //compareAndSetHead入队操作;把这个nn设置成为队列当中的头部,cas防止多线程、确保原子操作;
            //记住这个时候队列当中只有一个,即nn
            if (compareAndSetHead(new Node()))
                //这个时候AQS队列当中只有一个元素,即头部=nn,所以为了确保队列的完整,设置头部等于尾部,即nn即是头也是尾
                //然后第一次循环结束;接着执行第二次循环,第二次循环代码我写在了下面,接着往下看就行
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}


//为了方便 第二次循环我再贴一次代码来对第二遍循环解释
private Node enq(final Node node) {//这里的node就是当前线程封装的node也就是nc
    //死循环
    for (;;) {
        //对尾复制给t,由于第二次循环,故而tail==nn,即new出来的那个node
        Node t = tail;
        //第二次循环不成立
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //不成立故而进入else
            //首先把nc,当前线程所代表的的node的上一个节点改变为nn,因为这个时候nc需要入队,入队的时候需要把关系维护好
            //所谓的维护关系就是形成链表,nc的上一个节点只能为nn,这个很好理解
            node.prev = t;
            //入队操作--把nc设置为对尾,对首是nn,
            if (compareAndSetTail(t, node)) {
                //上面我们说了为了维护关系把nc的上一个节点设置为nn
                //这里同样为了维护关系,把nn的下一个节点设置为nc
                t.next = node;
                //然后返回t,即nn,死循环结束,enq(node);方法返回
                //这个返回其实就是为了终止循环,返回出去的t,没有意义
                return t;
            }
        }
    }
}

  //这个方法已经解释完成了
  enq(node);
  //返回nc,不管哪种情况都会返回nc;到此addWaiter方法解释完成
  return node;


//再次贴出node的结构方便大家查看
public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
}



-------------------总结:addWaiter方法就是让nc入队-并且维护队列的链表关系,但是由于情况复杂做了不同处理
-------------------主要针对队列是否有初始化,没有初始化则new一个新的Node nn作为对首,nn里面的线程为null
-------------------接下来分析acquireQueued方法

acquireQueued(addWaiter(Node.exclusive),arg))经过上面的解析之后可以理解成为acquireQueued(nc,arg))

acquireQueued方法的源码分析
final boolean acquireQueued(final Node node, int arg) {//这里的node 就是当前线程封装的那个node 下文叫做nc
    //记住标志很重要
    boolean failed = true;
    try {
        //同样是一个标志
        boolean interrupted = false;
        //死循环
        for (;;) {
            //获取nc的上一个节点,有两种情况;1、上一个节点为头部;2上一个节点不为头部
            final Node p = node.predecessor();
            //如果nc的上一个节点为头部,则表示nc为队列当中的第二个元素,为队列当中的第一个排队的Node;
            //这里的第一和第二不冲突;我上文有解释;
            //如果nc为队列当中的第二个元素,第一个排队的则调用tryAcquire去尝试加锁---关于tryAcquire看上面的分析
            //只有nc为第二个元素;第一个排队的情况下才会尝试加锁,其他情况直接去park了,
            //因为第一个排队的执行到这里的时候需要看看持有有锁的线程有没有释放锁,释放了就轮到我了,就不park了
            //有人会疑惑说开始调用tryAcquire加锁失败了(需要排队),这里为什么还要进行tryAcquire不是重复了吗?
            //其实不然,因为第一次tryAcquire判断是否需要排队,如果需要排队,那么我就入队;
            //当我入队之后我发觉前面那个人就是第一个,持有锁的那个,那么我不死心,再次问问前面那个人搞完没有
            //如果他搞完了,我就不park,接着他搞我自己的事;如果他没有搞完,那么我则在队列当中去park,等待别人叫我
            //但是如果我去排队,发觉前面那个人在睡觉,前面那个人都在睡觉,那么我也睡觉把---------------好好理解一下
            if (p == head && tryAcquire(arg)) {
                //能够执行到这里表示我来加锁的时候,锁被持有了,我去排队,进到队列当中的时候发觉我前面那个人没有park,
                //前面那个人就是当前持有锁的那个人,那么我问问他搞完没有
                //能够进到这个里面就表示前面那个人搞完了;所以这里能执行到的几率比较小;但是在高并发的世界中这种情况真的需要考虑
                //如果我前面那个人搞完了,我nc得到锁了,那么前面那个人直接出队列,我自己则是对首;这行代码就是设置自己为对首
                setHead(node);
                //这里的P代表的就是刚刚搞完事的那个人,由于他的事情搞完了,要出队;怎么出队?把链表关系删除
                p.next = null; // help GC
                //设置表示---记住记加锁成功的时候为false
                failed = false;
                //返回false;为什么返回false?下次博客解释---比较复杂和加锁无关
                return interrupted;
            }
            //进到这里分为两种情况
            //1、nc的上一个节点不是头部,说白了,就是我去排队了,但是我上一个人不是队列第一个
            //2、第二种情况,我去排队了,发觉上一个节点是第一个,但是他还在搞事没有释放锁
            //不管哪种情况这个时候我都需要park,park之前我需要把上一个节点的状态改成park状态
            //这里比较难以理解为什么我需要去改变上一个节点的park状态呢?每个node都有一个状态,默认为0,表示无状态
            //-1表示在park;当时不能自己把自己改成-1状态?为什么呢?因为你得确定你自己park了才是能改为-1;
            //不然你自己改成自己为-1;但是改完之后你没有park那不就骗人?
            //你对外宣布自己是单身状态,但是实际和刘宏斌私下约会;这有点坑人
            //所以只能先park;在改状态;但是问题你自己都park了;完全释放CPU资源了,故而没有办法执行任何代码了,
            //所以只能别人来改;故而可以看到每次都是自己的后一个节点把自己改成-1状态
            //关于shouldParkAfterFailedAcquire这个方法的源码下次博客继续讲吧
            if (shouldParkAfterFailedAcquire(p, node) &&
                //改上一个节点的状态成功之后;自己park;到此加锁过程说完了
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}



public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

加锁过程总结:
如果是第一个线程tf,那么和队列无关,线程直接持有锁。并且也不会初始化队列,如果接下来的线程都是交替执行,那么永远和AQS队列无关,都是直接线程持有锁,如果发生了竞争,比如tf持有锁的过程中T2来lock,那么这个时候就会初始化AQS,初始化AQS的时候会在队列的头部虚拟一个Thread为NULL的Node,因为队列当中的head永远是持有锁的那个node(除了第一次会虚拟一个,其他时候都是持有锁的那个线程锁封装的node),现在第一次的时候持有锁的是tf而tf不在队列当中所以虚拟了一个node节点,队列当中的除了head之外的所有的node都在park,当tf释放锁之后unpark某个(基本是队列当中的第二个,为什么是第二个呢?前面说过head永远是持有锁的那个node,当有时候也不会是第二个,比如第二个被cancel之后,至于为什么会被cancel,不在我们讨论范围之内,cancel的条件很苛刻,基本不会发生)node之后,node被唤醒,假设node是t2,那么这个时候会首先把t2变成head(sethead),在sethead方法里面会把t2代表的node设置为head,并且把node的Thread设置为null,为什么需要设置null?其实原因很简单,现在t2已经拿到锁了,node就不要排队了,那么node对Thread的引用就没有意义了。所以队列的head里面的Thread永远为null

《鲁班学院》子路老师spring底层原理分析视频全集.rar 整个视频课程将由浅入深,介绍spring5源码的构建、spring5IOC容器的初始化过程、bean的声明周期过程、spring BeanFactoryPostporcessor并且结合原理给出当前流行的应用框架如何利用spring的源码知识写出优雅的代码,比如mybaits的源码当中到底如何利用了spring的扩展点写出优雅的代码。 立即下载

相关推荐

从论文到生产:iSLIP优先级匹配算法在SDN交换机中的20年演进史

本文回顾了iSLIP优先级匹配算法在SDN交换机中的20年演进历程,从斯坦福大学的原始论文到现代芯片实现。通过独特的指针滑动和迭代循环机制,iSLIP算法显著提升了交换机的吞吐量和公平性,解决了传统FIFO调度的队头阻塞问题。文章详细探讨了算法在硬件实现中的工程挑战、开源实践及未来AI时代的智能调度方向。

weixin_29230649的博客 330

Spring源码解析

好的资源应该让更多人看见!这里列出子路老师的源码解析过程,真的很详细。 spring源码系列(一)——spring循环引用 spring源码系列(二)——毁三观的spring自动注入 spring源码系列(三)——beanDefinition(1) spring源码系列(四)——beanDefinition(2) 对应的视频课程网上也有。 ...

weixin_44651555的博客 260

第8.1节 满足构网要求的功率器件与散热设计

摘要: 构网型变流器的功率器件选型与散热设计面临严苛要求,需综合考虑过载能力、暂态应力与可靠性。IGBT模块选型需重点评估电压/电流等级、损耗特性及结温波动,而SiC MOSFET虽具有高频低损耗优势,但面临栅极可靠性等挑战。散热设计需采用液冷等先进技术控制结温波动,并通过电热应力仿真和寿命预测实现可靠性优化。系统化选型流程需结合规范要求、工况谱分析和实测验证,为构网型变流器提供坚实的物理基础。

技术工作者 912

spring源码系列(二)——毁三观的spring自动注入

比如提到spring的自动注入作为一个java程序员肯定自信无比了解;但是笔者要说的自动注入可能会和你理解有很大出入。 首先搞明白什么是自动注入,自动注入也可以叫做自动装配(springboot也有一个自动装配但是我认为翻译的不够准确,springboot的应该叫做自动配置和这里说的自动注入是两回事,笔者不是什么大牛或者权威;所以读者如果你坚持认为springboot也叫自动装配那也无可厚非,只...

java_lyvee的专栏 3万+

保姆级教程:在Jetson Nano上从零移植OV5647 CSI摄像头驱动(附完整调试记录)

本文提供了一份详细的Jetson Nano OV5647 CSI摄像头驱动移植教程,涵盖硬件准备、驱动框架分析、设备树配置、驱动移植核心修改点及调试技巧。通过V4L2框架和Tegra平台适配,开发者可快速实现摄像头驱动的移植与优化,适用于边缘计算和嵌入式视觉应用。

weixin_27869497的博客 518

大宗农产品交易知识图谱推理引擎的构建--需求分析

大宗农产品交易知识图谱推理引擎的构建 一、项目概述 ​ 进入信息时代,全球产生的数据量爆炸式增长,一方面数据产生的方式发生改变,由手工产生越来越多地转为自动化生成,另一方面,人类的活动也越来越依赖数据;民以食为天,粮食方面的信息数据对人类来说至关重要,而当前繁杂的数据已经超出了人工处理的范围,这就对数据采集和数据处理提出了新的要求。 知识图谱是将大量收集的数据整理成机器能处理的知识库,并实现可视化的展示。知识图谱本质上是一种大规模的语义网络,其主要目的是对真实世界里的实体或概念之间的关联关系进行描述;构造

qq_40583949的博客 2585

动态代理学习笔记

声明: 有一些博文是看的黑马程序员视频,然后跟着老师做的笔记 Spring是跟子路老师学的 特此感谢,写这些文章的目的是为了自己方便查阅

JDS_DIJ的博客 235

OpenCASCADE:Direct3D CSharp 示例

OpenCASCADE:Direct3D CSharp 示例OpenCASCADE:Direct3D CSharp 示例 OpenCASCADE:Direct3D CSharp 示例 此示例演示了如何在使用CSharp和Windows Presentation Foundation (WPF)编写的.Net应用程序中使用 OCCT 和 DirectX 库。 .Net、OCCT (C++) 和 DirectX 级别之间的连接由用 C++/CLI 编写的代理库OCCProxy和D3DProxy提供。代理库OCC

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1824

并发编程九:深入理解AQS和ReentrantLock源码分析

深入理解AQS和ReentrantLock源码分析 回顾下之前说到的管程模型MESA。这个模型当中有一个同步等待队列,获取锁失败的线程都会放入到这队列当中;还有一些条件等待队列,获取到锁的线程因为条件比如wait等待后,会进入条件等待队列。 之前说了synchronized是jvm层面对管程的实现。现在来说下jdk层面如何实现管程。 深入理解AQS 既然在jvm层面实现了管程,为什么还要从jdk(java)层面实现呢?首先synchronized的加锁解锁都是自动的,如果像通过代码控制什么时候加锁,什么时候

admin522043032的博客 678

GEE土地分类:一文读懂精度评定—混淆矩阵confusion matrix、整体精度OA、用户者精度、生产者精度、kappa系数和F1score系数含案例

本文介绍了如何在 Google Earth Engine (GEE) 中构建和分析混淆矩阵,以评估分类模型的性能。首先,通过 ee.Array 构建混淆矩阵,并展示了如何指定自定义类标签。接着,计算了多种性能指标,包括准确率、Kappa 值、F1-score、消费者准确率和生产者准确率。此外,还演示了如何计算不同类别的 F1-score。最后,通过一个实际案例,展示了如何利用 GEE 进行土地分类,并评估分类器的性能,包括训练和验证样本的准确率、Kappa 值等指标。这些步骤帮助用户理解模型的优缺点,并进行

此星光明博客 442

关于React Native Android找不到node的错误

最近,按照官方文档初始化了一个React Native项目,但是在Android端运行的时候,报了如下的一个错误。 org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:recordFilesBeforeBundleCommandDebug'. Caused by: org.gradle.process.internal.ExecException: A problem occurred starting p

xiangzhihong8的专栏 4246

深入理解AQS之ReentrantLock源码分析

深入理解AQS之ReentrantLock源码分析

qq_67801847的博客 214

RK3588开发笔记-spi扩展canfd芯片MCP2518FD调试笔记

在嵌入式系统开发中,CANFD(控制器局域网灵活数据速率)是一种广泛应用于汽车和工业自动化领域的通信协议。为了满足项目需求,我们在RK3588平台上通过SPI接口扩展了CANFD芯片MCP2518FD,之所以用spi进行canfd扩展,是为了解决原生RK3588原生can不够稳定的问题(发送扩展帧有几率编程标准帧,RK3568/3588均存在该问题)。通过本次调试工作,我们成功地在RK3588平台上扩展了CANFD芯片MCP2518FD,并实现了与CAN总线的稳定通信。

flypig has a dream! 4222

JUC(一)-ReentrantLock源码分析

JUC、ReentrantLock分析

~奇思妙想的王多鱼的博客 806

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

JUC的AQS学习-ReentrantLock源代码分析(转)

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33975951的博客 196

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

【JUC系列-05】通过源码分析AQS和ReentrantLock的底层原理

深入理解AQS和ReentrantLock的底层实现原理

zhenghuishengq的博客 7087

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【JUC篇】AQS、ReentrantLock详解

​是一个可重入且独占式的锁,基于AQS实现【AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架从ReentrantLock的实现看AQS的原理及应用 | JavaGuide】,和关键字类似。不过,更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。​AQS是一个抽象类,其内部定义了同步队列Condition单向链表等数据结构,对外提供了一系列的方法,用于构建定制化的锁和同步器;然后AQS内部也封装了一系列的原子操作,使用AQS可以非常轻松的实现一个自定义的锁。

m0_53732433的博客 1051

深入理解AQS独占锁之ReentrantLock源码分析

上一章我们讲解了ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等同步工具类的应用场景及实现,本章我们重点讲解ReentrantLock源码分析。

Myname_China的博客 1322

【锁】【JUC】可重入锁/AQS队列--ReentrantLock源码分析

1.重入锁/AQS队列 之前有写过一篇关于锁的笔记:【锁】公平锁/非公平锁/可重入锁/递归锁/自旋锁/独占锁/共享锁/读写锁 里面关于重入锁,特别AQS队列并没有提到,故借学习ReentrantLock源码几下这篇笔记。 2. ReentrantLock源码分析 2.1 类图 今天啰嗦一些,我们一步一步看: idea找到依赖的jdk下面rt.jar里面JUC包里面的ReentrantLock右...

aaa_bbb_ccc_123_456的博客 3045

ReentrantLock 源码分析以及 AQS (一)

前言 JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题。AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理。 AQS 中最主要的就是独占锁和共享锁的获取和释放,以及提供了一些可中断的获取锁,超时等待锁等方法。 ReentranLock 是基于 AQS 独占锁的一个实现。ReentrantReadWriteLoc...

烟雨星空的博客 1354
下一篇: spring源码系列(一)——spring循环引用
子路程序员
博客等级 码龄17年 8814粉丝 10原创
评论 89
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子路程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值