Java多线程之深入理解ReentrantLock

 更新时间:2021年04月25日 17:13:39   作者:EileenChang  
这篇文章主要介绍了Java多线程之深入理解ReentrantLock,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下

前言

保证线程安全的方式有很多,比如CAS操作、synchronized、原子类、volatile保证可见性和ReentrantLock等,这篇文章我们主要探讨ReentrantLock的相关内容。本文基于JDK1.8讲述ReentrantLock.

一、可重入锁

所谓可重入锁,即一个线程已经获得了某个锁,当这个线程要再次获取这个锁时,依然可以获取成功,不会发生死锁的情况。synchronized就是一个可重入锁,除此之外,JDK提供的ReentrantLock也是一种可重入锁。

二、ReentrantLock

2.1 ReentrantLock的简单使用

public class TestReentrantLock {
	private static int i = 0;

	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		try {
		    lock.lock();
            i++;
        } finally {
            lock.unlock();
        }
        System.out.println(i);
	}
}

上面是ReentrantLock的一个简单使用案列,进入同步代码块之前,需要调用lock()方法进行加锁,执行完同步代码块之后,为了防止异常发生时造成死锁,需要在finally块中调用unlock()方法进行解锁。

2.2 ReentrantLock UML图

在这里插入图片描述

2.3 lock()方法调用链

在这里插入图片描述

上图描述了ReentrantLock.lock()加锁的方法调用过程。在ReentrantLock中有一个成员变量private final Sync sync,Sync是AQS的一个子类。ReentrantLock的lock()方法中,调用了sync的lock()方法,这个方法为抽象方法,具体调用的是NonfairSync中实现的lock()方法:

/**
 * Performs lock.  Try immediate barge, backing up to normal
 * acquire on failure.
 */
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

在这个方法中,先尝试通过CAS操作进行加锁。如果加锁失败,会调用AQS的acquire()方法:

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

在AQS的acquire方法中,先尝试调用tryAcquire方法进行加锁,如果失败,会调用acquireQueued进入等待队列当中。acquireQueued方法将会在第三章中讲解,先来看tryAcquire方法的内容。AQS的tryAcquire方法是一个模板方法,其具体实现在NonfairSync的tryAcquire方法中,里面仅仅是调用了nonfairTryAcquire方法:

/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

在这个方法中,先获取state判断其是否为0。如果为0表示没有其他线程占用锁,会尝试通过CAS操作将state设为1进行加锁;如果state不为0,表示某个线程已经占用了锁,判断占用锁的线程是否为当前线程,如果是,则将state进行加1的操作,这就是ReentrantLock可重入的实现原理

三、AQS

AQS即AbstractQueuedSynchronizer。AQS提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。AQS其实是CLH(Craig,Landin,Hagersten)锁的一个变种,下面来讲解AQS的核心思想及其具体实现。

3.1 state

/**
     * The synchronization state.
     */
    private volatile int state;

state是AQS中最核心的成员变量。这是一个volatile变量,当其为0时,表示没有任何线程占用锁。线程通过CAS将state从0置为1进行加锁,当线程持有锁的情况下,再次进行加锁,会将state加1,即重入。

3.2 exclusiveOwnerThread

/**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;

exclusiveOwnerThread是AQS的父类AbstractOwnableSynchronizer中的成员变量,其作用是实现可重入机制时,用于判断持有锁的线程是否为当前线程。

3.3 AQS等待队列

除了以上state和exclusiveOwnerThread两个重要的成员变量以外,AQS还维护了一个等待队列。当线程尝试加锁失败时,会进入这个等待队列中,这也是整个AQS中最核心的内容。这个等待队列是一个双向链表,其节点Node对等待加锁的线程进行封装。

在这里插入图片描述

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
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;
        // 通过CAS操作将自身追加到链表尾部
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

当线程尝试加锁失败时,通过CAS操作将自身追加到链表尾部。入队之后,会调用acquireQueued在队列中尝试加锁:

/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在这个方法中,会判断其前置节点是否为头节点,如果是,则尝试进行加锁。如果加锁失败,则调用LockSupport.park方法进入阻塞状态,等待其前置节点释放锁之后将其唤醒。

3.4 AQS中的模板方法设计模式

AQS完美地运用了模板方法设计模式,其中定义了一系列的模板方法。比如以下方法:

// 互斥模式下使用:尝试获取锁
protected boolean tryAcquire(int arg) {
	throw new UnsupportedOperationException();
}
// 互斥模式下使用:尝试释放锁
protected boolean tryRelease(int arg) {
	throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试获取锁
protected int tryAcquireShared(int arg) {
	throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试释放锁
protected boolean tryReleaseShared(int arg) {
	throw new UnsupportedOperationException();
}

这些方法在AQS中只抛出了UnsupportedOperationException异常,所以需要子类去实现它们。之所以没有将这些方法设计成为抽象方法,是因为AQS的子类可能只需要实现其中的某些方法即可实现其功能。

总结

不同版本的JDK,AQS的实现可能会有细微的差异,但其核心思想是不会变的,即线程加锁失败后,通过CAS进行入队的操作,并通过CAS的方法设置state来获得锁。

到此这篇关于Java多线程之深入理解ReentrantLock的文章就介绍到这了,更多相关Java ReentrantLock总结内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Ribbon负载均衡服务调用的示例详解

    Ribbon负载均衡服务调用的示例详解

    Rbbo其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,这篇文章主要介绍了Ribbon负载均衡服务调用案例代码,需要的朋友可以参考下
    2023-01-01
  • 生产环境NoHttpResponseException异常排查解决记录分析

    生产环境NoHttpResponseException异常排查解决记录分析

    这篇文章主要为大家介绍了生产环境NoHttpResponseException异常排查解决记录分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 解决springboot 2.x 里面访问静态资源的坑

    解决springboot 2.x 里面访问静态资源的坑

    这篇文章主要介绍了解决springboot 2.x 里面访问静态资源的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java设计模式之备忘录模式实现对象状态的保存和恢复

    Java设计模式之备忘录模式实现对象状态的保存和恢复

    本文介绍Java设计模式之备忘录模式,该模式可以实现对象状态的保存和恢复。通过详细讲解备忘录模式的原理、实现方法和应用场景,帮助读者深入理解该设计模式,并提供示例代码和技巧,便于读者实际应用
    2023-04-04
  • Spring MVC接受表单自动封装特性实例解析

    Spring MVC接受表单自动封装特性实例解析

    这篇文章主要介绍了Spring MVC接受表单自动封装特性实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java中Reactor的反应器模式详解

    Java中Reactor的反应器模式详解

    这篇文章主要介绍了Java中Reactor的反应器模式详解,Reactor反应器模式有点儿类似事件驱动模式,当有事件触发时,事件源会将事件dispatch分发到handler处理器进行事件处理,反应器模式中的反应器角色类似于事件驱动模式中的dispatcher事件分发器角色,需要的朋友可以参考下
    2023-12-12
  • java使用dom4j生成与解析xml文档的方法示例

    java使用dom4j生成与解析xml文档的方法示例

    这篇文章主要介绍了java使用dom4j生成与解析xml文档的方法,结合实例形式分析了java基于dom4j操作xml节点生成xml文档以及解析xml文档的相关操作技巧,需要的朋友可以参考下
    2017-07-07
  • Flask实现异步非阻塞请求功能实例解析

    Flask实现异步非阻塞请求功能实例解析

    这篇文章主要介绍了Flask实现异步非阻塞请求功能实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • springboot 2.3之后消失的hibernate-validator解决方法

    springboot 2.3之后消失的hibernate-validator解决方法

    这篇文章主要介绍了springboot 2.3之后消失的hibernate-validator解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java实现字符串倒序输出的常用方法小结

    Java实现字符串倒序输出的常用方法小结

    这篇文章主要介绍了Java实现字符串倒序输出的常用方法,通过三个实例从不同角度实现该功能,有不错的借鉴价值,需要的朋友可以参考下
    2014-09-09

最新评论