前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ReentrantLock知识点梳理

ReentrantLock知识点梳理

作者头像
微观技术
发布2020-08-20 15:09:09
3190
发布2020-08-20 15:09:09
举报
文章被收录于专栏:微观技术微观技术

接下来几篇文章会对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

代码语言:javascript
复制
public void lock() {
    // sync,是构造器传入的,支持非公平模式、公平模式
    sync.lock();
}

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

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

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
代码语言:javascript
复制
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 失败,代表着锁争抢失败,进入排队竞争阶段

代码语言:javascript
复制
// 当前线程被包装成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;
}
代码语言:javascript
复制
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()
代码语言:javascript
复制
public void unlock() {
    sync.release(1);
}
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微观技术 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、基本信息
  • 二、源码分析
    • 1、获取锁 lock()
      • 2、释放锁 unlock()
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档