IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> ReentrantLock的源码解析 -> 正文阅读

[Java知识库]ReentrantLock的源码解析

首先先观察AQS AbstractQueuedSynchronizer里面的一些属性 我们会用到:

//没有注释的属性暂时不用看
//内部双向链表的头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;
//一个锁的状态为 0代表没有上锁  >0代表上了锁
private volatile int state;
//这个是我们的AQS中的静态内部类 主要是一个双向链表
static final class Node {
		
        static final Node SHARED = new Node();
        
        static final Node EXCLUSIVE = null;
		//该节点失效的标志位
        static final int CANCELLED =  1;
		//说明该节点的后继节点需要被唤醒的标志位
        static final int SIGNAL    = -1;
		//暂时了解
        static final int CONDITION = -2;
		//暂时不了解
        static final int PROPAGATE = -3;
        //这个里面装的就是上面的标志位
        volatile int waitStatus;
        
        //标志该节点的下一个节点
        volatile Node prev;
        //标志该节点的上一个节点
        volatile Node next;
        //该节点所占用的线程
        volatile Thread thread;
        Node nextWaiter;
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        //获取该节点的上一个节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
	
		//构造方法
        Node() {
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

接下来我们看ReentrantLock里面的一个NonfairSync 非公平锁:

为什么叫非公平锁 你看代码 一上来 队列都不进直接就开始抢锁了

//这个给sync类是继承我们的AQS的
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
		//这个方法是上锁操作
        final void lock() {
        	//这里是一个CAS操作 修改State状态为从 0 修改为 1 标志我们需要占用锁资源
        	//注意 这里修改的state不是Node节点的state 而是AQS中的state状态标志位 标志有无人占用锁资源
            if (compareAndSetState(0, 1))
            	//就将exclusiveOwnerThread设置为当前线程 这个属性是AQS的父类AbstractOwnableSynchronizer的一个属性
            	//private transient Thread exclusiveOwnerThread;
                setExclusiveOwnerThread(Thread.currentThread());
            else//如果失败 失败的情况就是发生了并发操作 表示有人提前占用锁资源
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

进入上面的Acquire方法种查看:

这个方法主要是重新获取锁资源 如果无法获取 就将当前线程放入队列中并且将线程挂起

public final void acquire(int arg) {
		//第一步调用我们的tryAcquire方法 传入参数为1 尝试重新获取锁资源
        if (!tryAcquire(arg) &&
        	//说明前面获取锁资源失败了 我们会放入到队列中去等待
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

进入tryAcquire方法中去看(它直接调用nonfairTryAcquire方法)这里面标志了一个很重要的点,如果将AOS的exclusiveOwnerThread设置为我们使用的线程,同时state>0则表明我们拿到了锁

//非公平锁来尝试获取锁资源
final boolean nonfairTryAcquire(int acquires) {
			//获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前的状态位 是否有人竞争锁资源的状态位
            int c = getState();
            //如果是0代表没有线程获取到了锁资源
            if (c == 0) {
            	//修改状态位从 0 -> 1
                if (compareAndSetState(0, acquires)) {
                	//修改成功 将属性值修改位当前线程 上面有这个方法 解释过了
                    setExclusiveOwnerThread(current);
                    return true;//返回true
                }
            }
            //如果当前有线程获取到了锁资源 查看这个线程是否和我们的线程一致
            else if (current == getExclusiveOwnerThread()) {
            	//如果是同一个线程 那么将state的值+1 因为是可重入锁 所以同一个线程是可以进入的
                int nextc = c + acquires;
                //这个就是指 +1过后值超过int可以表达的最大范围了 一个健壮性的判断
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                //修改状态位
                setState(nextc);
                //返回true
                return true;
            }
            //如果上面两个都没有成功 返回false代表获取锁资源失败
            return false;
        }

查看放入addWaiter放入队列等待的方法:

//锁资源获取失败 添加到队列中等待
private Node addWaiter(Node mode) {
		//新建一个node节点 里面是当前线程 和一个mode 其实这个参数就是null Node.EXCLUSIVE
        Node node = new Node(Thread.currentThread(), mode);
        //我们需要知道这个方法实在AQS中的方法 所以获取的tail就是整个队列的尾节点
        Node pred = tail;
        //如果尾节点是null 证明这个队列是空队列 没有放任何东西
        if (pred != null) {
        	//我们传入节点的前驱节点指向整个队列的尾节点
            node.prev = pred;
            //cas来修改整个队列的尾节点位我们传入的节点
            if (compareAndSetTail(pred, node)) {
            	//之前的尾节点的next节点指向我们传入的节点
                pred.next = node;
                return node;
            }
            //其实这整个操作就是一个修改队列的尾部节点的操作
        }
        //重复修改尾节点
        enq(node);
        return node;
}
观察enq方法:
private Node enq(final Node node) {
		//整体就是一次无限循环 直到我们修改尾节点成功
		//死循环
        for (;;) {
        	//获取尾部节点
            Node t = tail;
            //尾部节点为空
            if (t == null) {
            	//将头节点设置为一个新建的节点 这个节点里面是没有任何参数的 相当于一个哨兵
                if (compareAndSetHead(new Node()))
                	//头尾都指向这个个节点
                    tail = head;
                    //而且没有返回值 证明它还会循环一次进入下面的方法体
            } else {
            	//不为空证明之前修改尾节点失败
                node.prev = t;
                //重新指向操作
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
}

放入队列过后 会执行下一个操作,我们已经执行了addWaiter操作了:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

等待被唤醒 来抢夺我们的锁资源

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; //帮助GC回收垃圾 让他不指向任何对象GCroot不可达
                    //failed标志位false 说明没有失败
                    failed = false;
                    //返回interrupted 默认是false 表示不用中断线程
                    return interrupted;
                }
                //
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//执行线程挂起操作 返回值是当前线程是否中断 但是他会暂停在park的地方 直到有人把该线程唤醒
                    interrupted = true;
            }
        } finally {
        	//如果failed为true就代表失败了 上述只有很少的可能才会failed=true,一般都是方法中出现抛异常此操作   取消这个任务的操作
            if (failed)
                cancelAcquire(node);
        }
}
========================================================================================
setHead方法:
//置空的原因是我们的thread已经放入了exclusiveOwnerThread属性中,已经不需要再次保存了 它已经持有了锁
private void setHead(Node node) {
		//当前节点设置为头节点
        head = node;
        //同时将他的thread属性置空
        node.thread = null;
        //前驱节点置空
        node.prev = null;
}
========================================================================================
shouldParkAfterFailedAcquire方法:
//检查当前节点的前驱节点是否有效 如果是SIGNAL直接返回true
//如果如果是无效就将该节点前面的无效节点全部从队列中剔除
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		//获取前驱节点的状态位
        int ws = pred.waitStatus;
        //如果前驱节点的标志位是SIGNAL 表明它的后继节点 就是该节点需要被唤醒
        if (ws == Node.SIGNAL)
            return true;
        //如果标志位>0 只有CANCELLED 这种情况 表明前驱节点失效了
        if (ws > 0) {
        	//循环查找前驱节点状态位不是CANCELLED 的节点
        	//查找的同时修改我们的指针
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //找到了该节点把它的next指向我们的当前节点
            pred.next = node;
            //整体就是一个让我们的当前节点去修改它的前驱节点 把无效的节点剔除队列
        } else {
            //如果前驱节点的状态位是 -2 或者 -3 的话 就将他修改为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //后面两种情况执行完后会返回false
        return false;
}
========================================================================================
parkAndCheckInterrupt方法:
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//将当前线程挂起
        return Thread.interrupted();//返回线程是否中断
}
========================================================================================
cancelAcquire方法:
private void cancelAcquire(Node node) {
        //健壮性的判断
        if (node == null)
            return;
		//设置当前节点的thread属性为null
        node.thread = null;

        //得到当前节点的前驱节点
        Node pred = node.prev;
        //如果前驱节点是CANCELLED状态 就继续向上找 匆匆队列中剔除这些出错的节点
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

		//这个前驱节点的next指向的节点 注意:并不是当前节点 因为还没有赋值操作
        Node predNext = pred.next;
		//当前节点的标志位设置为CANCELLED 因为已经进入了取消认任务的方法 标志当前任务可以不用执行了
        node.waitStatus = Node.CANCELLED;

        //如果当前节点是AQS队列的尾节点 进行CAS修改队列尾节点指向我们之前获取的有效的那个前驱节点
        if (node == tail && compareAndSetTail(node, pred)) {
        	//指向成功后将该前驱节点的next置为null,因为它已经是尾节点了
            compareAndSetNext(pred, predNext, null);
        } else {
            //当我们的当前节点并不是尾节点
            int ws;
            //如果前驱节点不是头节点
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||//前驱节点的状态位为SIGNAL或者是状态位位 -2 -3 只要不为CANCELLED 我们吧它的状态位修改位SIGNAL
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null)//前驱节点的线程属性不为空表明它还没有执行任务 {
                Node next = node.next;//获取我们node节点的next属性
                if (next != null && next.waitStatus <= 0)//找到一个节点状态位不为CANCELLED将他作为该前驱节点的后继节点 剔除中间的状态位为CANCELLED的节点
                    compareAndSetNext(pred, predNext, next);
            } else {
            	//其他情况  从尾部开始找可以启动的节点 唤醒该节点的线程
                unparkSuccessor(node);
            }
            node.next = node; //方便GC回收垃圾
        }
}
========================================================================================
private void unparkSuccessor(Node node) {
        //获取当前节点的状态位
        int ws = node.waitStatus;
        //如果状态位不是CANCELLED
        if (ws < 0)
        	//修改为0
            compareAndSetWaitStatus(node, ws, 0);
		//当前节点的后继节点
        Node s = node.next;
        //如果后继节点是空 或者 后继节点的状态位是CANCELLED
        if (s == null || s.waitStatus > 0) {
        	//s指向null
            s = null;
            //从尾节点开始寻找前驱节点 如果找到的节点为null 或者是 node节点 才停止
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)//如果不是CANCELLED
                    s = t;//将s指向该节点
        }
        //如果
        if (s != null)
            LockSupport.unpark(s.thread);//将该节点的线程唤醒
}

一个非常简化的非公平锁流程图:
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-09 18:09:53  更:2022-04-09 18:13:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:43:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码