`

ReentrantLock锁机制原理

 
阅读更多

因为ReentrantLock和ReentrantReadWriteLock的实现原理基本相同,就单看ReentrantLock。

 

第一步先看加锁

 final void lock() {
            if (compareAndSetState(0, 1))                              // 第一次尝试CAS指令来获取锁,若是失败的话,再通过acquire(1)方法获取锁。
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

  acquire(1)方法的实现是一个非公平的偏向锁,

 

  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))      // 这里若是获取锁失败的话,那么加入等待队列,是一个CLH的实现!
            selfInterrupt();
    }

  tryAcquire 就是JAVA的偏向锁实现,,先看看最tryAcquire的实现:

 

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {                                        // c==0 代表当前没有没有线程竞争锁,因为是可重入锁,所以通过CPU的指令对acquires+1 在释放锁的时候会对acquires-1
                if (compareAndSetState(0, acquires)) {           // 这里并没有指定是队列中的第一个元素,是所有可竞争的线程,所以才是它不公平的地方,排队不一定有用!
                    setExclusiveOwnerThread(current);            //标示当前线程为获取锁的线程,并返回true
                    return true;                   
                }
            }
            else if (current == getExclusiveOwnerThread()) {    // 这里是偏向锁实现的关键,重入后还是自己持有锁,那么不去执行CAS操作获取锁等操作导致的时间延迟,
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

  看看addWaiter(Node.EXCLUSIVE), arg) 是如何实现的吧

 

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) {                         // 若是尾节点不为空的话,那么设置新进入的线程上一个节点是尾节点,同时通过CAS将当前线程设置为尾节点!
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);                                   // 若是尾节点为空,具体看看enq方法
        return node;
    }


 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize            // 若是尾节点为空的话,那么新建一个假的节点并设置为首节点
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;                             // 将首节点设置为当前节点的前节点 
                if (compareAndSetHead(h)) { 
                    tail = node;
                    return h;
                }
            }
            else {                                        // 若是不为空,直接设置当前节点为尾节点
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

  总的说 addWaiter方法就是将线程加入到队列的尾部。

 

  在回过头来看看  acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 中 acquireQueued方法做的工作:

  final boolean acquireQueued(final Node node, int arg) {

        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {                // 若是上一个节点是头结点的话,那么尝试当前线程获取锁,因为你后面没有线程了(至于为什么后面没有线程在后面会解释)
                    setHead(node);                                 // 执行了当前线程后,并将当前线程设置为头线程,置后节点为空。
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&       // 若是自己的前节点不是头节点,或者没有竞争到锁的话,那么park当前线程 
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

  先看 shouldParkAfterFailedAcquire干了什么 

 

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;                                         //在第一次进入的时候头节点是空对象,所以它的waitStatus就是默认值0
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
	    do {
		node.prev = pred = pred.prev;
	    } while (pred.waitStatus > 0);                              // 清理那些一直未拿到锁,并最终抛出异常的线程!
	    pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE. Indicate that we
             * need a signal, but don't park yet. Caller will need to
             * retry to make sure it cannot acquire before parking. 
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);           // 这个时候就会尝试将头结点的waitStatus设置为SIGNAL状态,就是-1,
        } 
        return false;
    }

    在第一次进入的时候头节点是空对象,所以它的waitStatus就是默认值0,在结果返回false后,在外层的 acquireQueued方法中是一个for(;;),下次再次进入的话 ,因为前节点已经是的waitStatus已经变成了SIGNAL,所以会返回true,那么就会执行 

       if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 中的 parkAndCheckInterrupt,这个方法具体如下所示:

       private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);                       // 简单的park当前没有抢到锁的线程! 
        return Thread.interrupted();
    }

    此刻就会进入:

 final boolean acquireQueued(final Node node, int arg) {

        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);                    // 在前面park自己的线程后
            throw ex;
        }
    }

  因为总有一个线程会抢到锁,所以再来看看unlock会干什么事情。

    public final boolean release(int arg) {

        if (tryRelease(arg)) {                 //因为是可重入锁,那么就先减少1
            Node h = head;                     
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);            进行队列中线程的unpark,从头部节点开始,从这里看到,
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling. It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);                     // 将头部节点置为 0 ,因为在 final boolean acquireQueued(final Node node, int arg) {函数中,我们是将一个拿到锁的线程置为了头部节点,所以需要将那个原本执行的线程重置为一个初始状态!

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)  // 这里就是最关键的地方了,找到当前执行线程的下一个节点,参数传入的是头节点,那么就是找到头节点后一个处在可运行状态的节点,并进行unpark,到这里我们就可以清晰的看出整体中是一个不完全的先进先出队列,不完全是因为就算unpark了你,当你还需要去跟其他未进入队列的线程竞争,若是竞争失败的话,还的乖乖的回到原点,继续等待!
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
分享到:
评论

相关推荐

    locks框架_ReentrantLock.pdf

    这份资源旨在详细讲解 Java 中的 Locks 框架,特别关注 ReentrantLock 的使用和原理。Locks 框架提供了比传统的 synchronized 关键字更强大、更灵活的线程同步机制,而 ReentrantLock 是其中的一种重要实现。 Locks ...

    Lock锁的底层原理完整版

    Lock锁,一种线程同步机制,其主要功能是防止多个线程同时访问同一代码块,从而避免因并发问题引发的数据不一致或其他错误。Lock锁的灵活性相比synchronized更高,它支持手动获取和释放锁,能够中断的获取锁以及超时...

    locks框架:接口.pdf

    可重入性和重入锁: 解释 Lock 接口的可重入性,讲解同一个线程多次获取锁的机制,避免死锁。介绍 ReentrantLock 的实现原理。 Condition 条件变量: 介绍 Lock 接口中的 Condition,它可以实现更复杂的线程等待和...

    高级开发并发面试题和答案.pdf

    ReentrantLock如何实现可重入性 volatile作用; wait 与 sleep 的有什么不同?回答的要点四个: Thread.sleep()和LockSupport.park()的区别 Object.wait()和LockSupport.park()的区别 线程和线程池 线程池的五种状态...

    并发锁核心类AQS学习笔记

    JUC 包中的同步类基本都是基于 AQS 同步器来实现的,如 ReentrantLock,Semaphore 等。 二、原理 1、AQS 工作机制: 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为...

    详解java多线程的同步控制

    目录线程安全 Thread Safety重入锁 ReentrantLock读写锁 ReadWriteLock倒计数器 CountDownLatch循环栅栏 CyclicBarrier信号量 Semaphore 线程安全 Thread Safety JMM JMM(Java Memory Model)是一种基于计算机内存...

    Java-Interview:此项目为 Java 面试的汇总,多数是一些 Java 基础知识、底层原理、算法详解。也有上层应用设计,其中不乏一些大厂面试真题

    ReentrantLock 实现原理 ConcurrentHashMap 的实现原理 线程池原理 深入理解线程通信 交替打印奇偶数 JVM Java 运行时内存划分 类加载机制 OOM 分析 垃圾回收 对象的创建与内存分配 你应该知道的 volatile 关键字 ...

    java-interview

    ReentrantLock 实现原理 ConcurrentHashMap 的实现原理 线程池原理 深入理解线程通信 交替打印奇偶数 JVM Java 运行时内存划分 类加载机制 OOM 分析 垃圾回收 对象的创建与内存分配 你应该知道的 volatile 关键字 ...

    Java-Interview:https

    ReentrantLock 实现原理 ConcurrentHashMap 的实现原理 线程池原理 深入理解线程通信 交替打印奇偶数 JVM Java 运行时内存划分 类加载机制 OOM 分析 垃圾回收 对象的创建与内存分配 你应该知道的 volatile 关键字 ...

    JDK自带多线程工具包详解

    由浅入深,通过图解和手写代码,讲解Java版的多线程,主要...线程同步+各种锁的原理&手写实现 JDK多线程工具包中,若干种工具的原理和手写实现: ReentrantLock、CountDownLanuh、CyclicBarrier、Semaphore      

    【2018最新最详细】并发多线程教程

    11.深入理解读写锁ReentrantReadWriteLock 12.详解Condition的await和signal等待通知机制 13.LockSupport工具 14.并发容器之ConcurrentHashMap(JDK 1.8版本) 15.并发容器之ConcurrentLinkedQueue 16.并发容器之...

    leetcode下载-study:学习笔记

    数据存储原理 Mysql 索引 abc 复合索引 数据库隔离级别 InnoDB 与 MySAIM 区别 Mysql MVCC JVM Java 类加载过程 Java 类加载机制 新生代频繁 gc 如何调整 CMS 垃圾回收器 锁 Lock 与 Sychronized 区别 Redis 分布式...

    Java面试题.docx

    46、谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解 49、synchronized 和volatile 关键字的区别 51-58题 51、ReentrantLock 、synchronized和volatile比较 53、死锁的四个必要条件? 56、什么是线程池...

    Java常见面试题208道.docx

    48.多线程锁的升级原理是什么? 49.什么是死锁? 50.怎么防止死锁? 51.ThreadLocal 是什么?有哪些使用场景? 52.说一下 synchronized 底层实现原理? 53.synchronized 和 volatile 的区别是什么? 54.synchronized...

    java核心知识点整理.pdf

    1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM .........................

    JAVA核心知识点整理(有效)

    1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM ........................

    汪文君高并发编程实战视频资源全集

    ReentrantLock详细讲解_.mp4  高并发编程第三阶段27讲 ReadWriteLock&amp;ReentrantReadWriteLock详细讲解_.mp4  高并发编程第三阶段28讲 Condition初步使用,提出几个疑问_.mp4  高并发编程第三阶段29讲 关于...

    汪文君高并发编程实战视频资源下载.txt

    ReentrantLock详细讲解_.mp4  高并发编程第三阶段27讲 ReadWriteLock&amp;ReentrantReadWriteLock详细讲解_.mp4  高并发编程第三阶段28讲 Condition初步使用,提出几个疑问_.mp4  高并发编程第三阶段29讲 关于...

    javaSE代码实例

    17.3.2 ReentrantLock锁的具体使用 387 17.3.3 ReadWriteLock接口与ReentrantReadWriteLock类简介 390 17.3.4 ReentrantReadWriteLock读/写锁的具体使用 391 17.4 信号量的使用 393 17.4.1 Semaphore类简介...

Global site tag (gtag.js) - Google Analytics