专栏首页微观技术ReentrantLock知识点梳理

ReentrantLock知识点梳理

接下来几篇文章会对JUC并发包里面的锁工具类做下梳理,如:ReentrantLock、

ReentrantReadWriteLock、StampedLock

一、基本信息

Lock 用于解决互斥问题,即同一时刻只允许一个线程访问共享资源;Condition 用于解决同步问题(即线程之间如何通信、协作)

ReentrantLock 类结构:

  • ReentrantLock 实现了Lock接口
    • 内部类:class Sync extends AbstractQueuedSynchronizer
    • 内部类:class NonfairSync extends Sync
    • 内部类:class FairSync extends Sync
  • 可重入锁
  • 默认是非公平模式
  • 主要方法:
    • lock():阻塞模式来获取锁
    • lockInterruptibly:阻塞式获取锁,支持中断
    • tryLock():非阻塞模式尝试获取锁
    • tryLock(long timeout, TimeUnit unit):同上,支持时间设置
    • unlock():释放锁
    • newCondition():创建条件变量
    • getHoldCount():当前线程对该锁的计数次数
    • isHeldByCurrentThread():锁是否被当前线程持有
    • isLocked():锁是否已经被某个线程持有
    • getQueuedThreads():获取排队的线程列表

AbstractQueuedSynchronizer 类结构:

  • 内部结构:
    • transient volatile Node head:
    • transient volatile Node tail:
    • volatile Thread thread:
    • volatile int state:反映锁的持有情况,当前线程获得锁,对其+1,释放锁对其 -1
  • 核心
    • 一个 volatile 的整数成员表征状态,同时提供了 setState 和 getState 方法
    • 一个先进先出(FIFO)的等待线程队列,以实现多线程间竞争和等待
    • 底层基于 CAS 的基础方法(Unsafe类),以及各种期望具体同步结构去实现的 acquire/release 方法

Condition 类结构:

当使用Lock来保证线程同步时,需使用Condition条件变量来使线程保持协调。Condition实例被绑定在一个Lock的对象上,使用Lock对象的方法newCondition()获取Condition的实例。Condition提供了下面三种方法,来协调不同线程的同步:

  • await():当前线程锁释放,并进入等待池中,直到其他线程调用该Condition的signal()或signalAll()方法唤醒该线程,重新去请求锁,拿到锁后,才可以继续进行后面操作。
  • signal():唤醒在此Lock对象上等待的单个线程。
  • signalAll():唤醒在此Lock对象上等待的所有线程。

synchronized与ReentrantLock比较:

  • 相同:
    • 都是可重入锁
  • 区别:
    • synchronized 是Java的一个内置关键字,而ReentrantLock是Java的一个类。
    • synchronized只能是非公平锁。而ReentrantLock可以实现非公平锁、公平锁两种。
    • synchronized 采用阻塞式,如果申请不到资源,线程直接进入阻塞状态,啥都干不了,也释放不了线程已经占有的资源(此时如果死锁,无法打破“不可剥夺条件”)。相反Lock更加灵活,支持非阻塞地获取锁、支持超时、支持响应中断。详细内容
    • synchronized采用隐式加锁,Lock采用显式加锁lock()、unLock()
    • synchronized会自动释放锁,而ReentrantLock不会自动释放锁,必须手动释放,否则可能会导致死锁。
    • synchronized只有一个Condition条件变量。Lock支持多个。详细过程

二、源码分析

1、获取锁 lock()

java.util.concurrent.locks.ReentrantLock#lock

public void lock() {
    // sync,是构造器传入的,支持非公平模式、公平模式
    sync.lock();
}

java.util.concurrent.locks.ReentrantLock.NonfairSync#lock

final void lock() {
    // 借助 unsafe 原子性对 state 加1。如果初始值为0,表示没有线程占有锁
    if (compareAndSetState(0, 1))
        // 设置当前线程独占锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 尝试获取锁
        acquire(1);
}
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 获取当前 AQS 内部状态量
    int c = getState();
    // 0 表示无线程占有,直接用 CAS 修改
    if (c == 0) {
        // 不检查排队情况,直接争抢
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 可重入锁情况
    else if (current == getExclusiveOwnerThread()) {
        // state 计数增加
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

如果前面的 tryAcquire 失败,代表着锁争抢失败,进入排队竞争阶段

// 当前线程被包装成EXCLUSIVE排他模式的节点,通过addWaiter方法添加到队列中
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 无限循环
        for (;;) {
            // 当前节点的前一个节点
            final Node p = node.predecessor();
            // 如果前一个节点是头结点,表示当前节点适合去 tryAcquire
            if (p == head && tryAcquire(arg)) {
                // if 获取成功,设置当前节点为头节点,出队列
                setHead(node);
                // 将前面节点对当前节点的引用清空
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果返回true,需要阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 借助sun.misc.Unsafe#park 执行阻塞
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    // 普通挂起。直到另一个持有锁线程释放锁后,触发下一个线程的 sun.misc.Unsafe#unpark
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

2、释放锁 unlock()

public void unlock() {
    sync.release(1);
}
}

本文分享自微信公众号 - 微观技术(weiguanjishu),作者:TomGE

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 史上最全ThreadPoolExecutor梳理(上篇)

    Java是面向对象编程,万事万物皆对象,讲究池化技术,可以避免对象频繁的创建、销毁,浪费性能。线程池作为线程的复用利器,工作中都用过,可以说是非常非常重要。面试...

    用户7676729
  • 一文全面梳理各种锁机制

    假设数据一般情况下不会造成冲突,只有在数据进行提交更新时,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回错误信息,让用户决定如何去做。fail-fas...

    用户7676729
  • 血泪教训,线程池引发的内存泄露

    最近由于业务需求使用到了线程池对数据进行异步处理,上线后系统正常运行了两天多突然收到了一波Full GC的告警,赶紧dump了堆信息并回滚了服务。分析dump文...

    用户7676729
  • JAVA-LOCK之底层实现原理(源码分析)

    首先和Synchronized(可以参考) 的不同之处,Lock完全用Java写成,在java这个层面是无关JVM实现的。其实现都依赖java.util.con...

    海涛
  • 【深入AQS原理】我画了35张图就是为了让你深入 AQS

    谈到并发,我们不得不说AQS(AbstractQueuedSynchronizer),所谓的AQS即是抽象的队列式的同步器,内部定义了很多锁相关的方法,我们熟知...

    一枝花算不算浪漫
  • AbstractQueuedSynchronizer超详细原理解析

     今天我们来研究学习一下AbstractQueuedSynchronizer类的相关原理,java.util.concurrent包中很多类都依赖于这个类所提供...

    程序员历小冰
  • 多线程基础(十七):Condition及ConditionObjet源码分析

    在java中,为了配合ReentrantLock等Lock的实现类实现锁的多条件等待,为此java设计了Condition接口。在AQS中的主要结构如下:

    冬天里的懒猫
  • Java并发指南7:JUC的核心类AQS详解

    本文转自:https://www.javadoop.com/post/AbstractQueuedSynchronizer#toc4

    Java技术江湖
  • AbstractQueuedSynchronizer原理剖析

    无论是公平锁还是非公平锁,它们的实现都依赖于AbstractQueuedSynchronizer,它提供了一个基于先进先出等待队列 实现block locks和...

    爬蜥
  • 面试官:谈一谈java中基于AQS的并发锁原理

    我:java中的AQS是指AbstractQueuedSynchronizer类,java中并发锁控制逻辑都是基于这个类来实现的。

    jinjunzhu

扫码关注云+社区

领取腾讯云代金券