并发编程笔记一-ReentrantLock源码深度解析

一. 深入ReentrantLock

1 ReentrantLock和synchronized有什么区别?

1. ReentrantLock是java.util.concurrent.locks下的某个工具类,而synchronized属于java当中的关键字,不过二者都属于JVM层面实现的互斥锁。


2. 使用方式不同,ReentrantLock可以使用lock(),trylock(),lockInterruptibly()等方法进行加锁,但释放锁同时也是手动去操作的,一般往往都是使用try finally{}代码中,调用unlock方法对锁进行释放。而synchronized作为关键字,用在类上,方法上以及代码块中,使用时都是针对某一个对象或者类对象进行加锁,同时在使用完毕后,它会自己对锁进行释放,对新手来说比较友好。


3. ReentrantLock比synchronized的功能更全面,使用起来也更灵活,如通过ReentrantLock可以实现公平锁,而synchronized关键字只有非公平锁的实现。且ReentrantLock还可以通过如tryLock()等方法去控制获取锁资源的超时时间,而synchronized也是做不到这点的。


4. JDK1.5版本时,推出了ReentrantLock,此时lock的性能是高于synchronized关键字的,当时的sychronized只有悲观锁的一个功能。而在JDK1.6版本,java团队对synchronized做了一系列的优化,如锁消除,锁膨胀和锁升级,优化过后的synchronized性能与ReentrantLock性能几乎无异。

二者如何选择:如果对并发编程比较熟练的开发建议用ReentrantLock,因为它的功能更加丰富,使用起来也更加灵活。如果对并发编程掌握程度比较一般,还是推荐使用synchronized。

2 AQS

AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatchSemaphore,CyclicBarrier等等都是基于AQS实现。

首先AQS中提供了一个由volatile修饰,并且采用CAS方式修改的int类型的state变量。

其次AQS中维护了一个双向链表,有head,有tail,并且每个节点都是Node对象。

以下,是AQS的内部类:Node的源码(注解是按照自己理解加的,与本次学习ReentrantLock无关的,一律都加了可忽略标识,可先不用关注它们),关于AQS,我也有会一个专题对其进行更详细的解析。

  static final class Node {
        
        // 本次学习可暂时忽略(共享锁下会用到节点)
        static final Node SHARED = new Node();
        
        // 本次学习可暂时忽略(排他锁下会用到节点)
        static final Node EXCLUSIVE = null;
        
        // 节点状态,1代表该节点被取消。
        static final int CANCELLED =  1;

        // 节点状态,代表该节点正常且可被唤醒。
        static final int SIGNAL    = -1;
        
        // 本次学习可暂时忽略(代表节点处于等待条件执行的状态)
        static final int CONDITION = -2;
        
        // 本次学习可暂时忽略(表示下一个acquireShared应无条件传播)
        static final int PROPAGATE = -3;

        // 非常关键:这是锁(节点)的等待状态,上面几个static final修饰的常量可以理解为它的枚        举值。
        volatile int waitStatus;

        // 前置节点
        volatile Node prev;

        // 后继节点
        volatile Node next;
        
        // 当前节点所绑定的线程
        volatile Thread thread;
        
        // 本次学习可忽略(链接到下一个节点等待条件,或特殊值SHARED。因为只有在独占模式下保持时才能访问条件队列,所以我们只需要一个简单的链接队列来在节点等待条件时保持它们。然后,它们被转移到队列中以重新获取。由于条件只能是独占的,我们通过使用特殊值来指示共享模式来保存字段。)
        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) {  
            this.nextWaiter = mode;
            this.thread = thread;
        }

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

根据上述源码,我们可以大致去构造一下我们本次学习需要了解的AQS内部节点的结构和属性,如下图2-1 AQS内部构造图所示。

        图2-1 AQS内部构造图所示

3 加锁流程源码解析

 3.1 加锁流程概述

本次我们要学习ReetrantLock的整体加锁流程,大致如下图3-1 加锁流程示例图,图中也有详细的文字对流程进行细化描述。

图3-1 加锁流程示例图

3.2  三种加锁源码解析

        在了解完ReentrantLock的大致加锁流程后,我们再来阅读加锁的源码,你就会发现原来是如此的简单。这也是我们阅读源码的一个常用技巧,先了解它是做什么的,流程是什么,再带着对它的理解去仔细阅读它的源码(注:以下源码版本都是基于JDK1.8)。

        3.2.1 lock()方法解读

        1. ReentrantLock是分公平锁与非公平锁的,因此,我们点进lock方法后,会发现公平锁与非公平锁的执行套路是不一样的,如下源码示例。

// 非公平锁
final void lock() {
    // 直接以CAS的方式进行锁资源抢占,具体方式是看当前线程是
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值