ReentrantLock 详解

ReentrantLock的功能是实现代码段的并发访问控制,也就是通常意义上所说的锁,java中实现锁有两种方式,一种是本文所提的ReentrantLock,另一种是synchronized。ReentrantLock相比synchronized 使用可以更灵活,这次就来看看ReentrantLock的内部实现。

我们首先看下ReentrantLock的锁是如何实现的

其实就一行代码 ,看起来很简单,那么这里的sync是什么呢?

这是ReentrantLock内部的一个抽象类,继承了AbstractQueuedSynchronizer(AQS),ReentrantLock所有的功能都和这个类有关

用过ReentrantLock的人都知道,ReentrantLock是分为公平锁和非公平锁,这在ReentrantLock内部是两种实现

公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁。

非公平锁:每个线程抢占锁的顺序不定,谁运气好,谁就获取到锁,和调用lock方法的先后顺序无关。

首先看下公平锁的内部实现

公平锁 加锁:



调用到了AQS的acquire方法:

再看下获取锁的代码

原来这里子类重写了

 1 protected final boolean tryAcquire(int acquires) {
 2             final Thread current = Thread.currentThread();
 3             //获取状态位
 4             int c = getState();
 5             //0,锁还没被拿走
 6             if (c == 0) {
 7                 //如果队列中没有其他线程 说明没有线程正在占有锁
 8                 if (!hasQueuedPredecessors() &&
 9                     //修改状态为1
10                     compareAndSetState(0, acquires)) {
11                     //如果通过CAS操作将状态为更新成功则代表当前线程获取锁,
12                     //因此,将当前线程设置到AQS的一个变量中,说明这个线程拿走了锁。
13                     setExclusiveOwnerThread(current);
14                     return true;
15                 }
16             }
17             //锁被拿走了,由于ReentrantLock是可重入锁,所以判断下持有锁的是否是同一个线程
18             else if (current == getExclusiveOwnerThread()) {
19                 //如果是的话累加在state字段上就可以了
20                 int nextc = c + acquires;
21                 if (nextc < 0)
22                     throw new Error("Maximum lock count exceeded");
23                 setState(nextc);
24                 return true;
25             }
26             return false;
27         }
28     }
29
30
31     protected final int getState() {
32         return state;
33     }

再回到acquire方法中

我们看下addWaiter方法

 1 private Node addWaiter(Node mode) {
 2         //用当前线程构造一个node,mode是一个表示Node类型的字段,
 3         //仅仅表示这个节点是独占的,还是共享的
 4         Node node = new Node(Thread.currentThread(), mode);
 5         // Try the fast path of enq; backup to full enq on failure
 6         Node pred = tail;
 7         //将节点插入到尾部
 8         if (pred != null) {
 9             node.prev = pred;
10             if (compareAndSetTail(pred, node)) {
11                 pred.next = node;
12                 return node;
13             }
14         }
15         //节点插入尾部失败,进入enq的死循环,知道插入成功
16         enq(node);
17         return node;
18     }

将线程的节点添加队里中后,还需要做一件事:将当前线程挂起!这个事,由acquireQueued来做。

 1 final boolean acquireQueued(final Node node, int arg) {
 2         boolean failed = true;
 3         try {
 4             boolean interrupted = false;
 5             for (;;) {
 6                 final Node p = node.predecessor();
 7                 //如果当前的节点是head说明他是队列中第一个“有效的”节点,因此尝试获取,这个方法子类重写了。
 8                 if (p == head && tryAcquire(arg)) {
 9                     setHead(node);
10                     p.next = null; // help GC
11                     failed = false;
12                     return interrupted;
13                 }
14                 ////否则,检查前一个节点的状态为,看当前获取锁失败的线程是否需要挂起。
15                 if (shouldParkAfterFailedAcquire(p, node) &&
16                     //如果需要,借助JUC包下的LockSopport类的静态方法Park挂起当前线程。直到被唤醒。
17                     parkAndCheckInterrupt())
18                     interrupted = true;
19             }
20         } finally {
21             if (failed)
22                 cancelAcquire(node);
23         }
24     }
1 private final boolean parkAndCheckInterrupt() {
2         LockSupport.park(this);
3         return Thread.interrupted();
4     }

这里需要在介绍下AQS中的Node节点的状态

黄色节点是默认head节点,其实是一个空节点,可以理解成代表当前持有锁的线程,每当有线程竞争失败,都是插入到队列的尾节点,tail节点始终指向队列中的最后一个元素。

/** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor‘s thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn‘t need to
         * signal. So, most code doesn‘t need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

每个节点中, 除了存储了当前线程,前后节点的引用以外,还有一个waitStatus变量,用于描述节点当前的状态。多线程并发执行时,队列中会有多个节点存在,这个waitStatus其实代表对应线程的状态:有的线程可能获取锁因为某些原因放弃竞争;有的线程在等待满足条件,满足之后才能执行等等。一共有4中状态:

CANCELLED 取消状态

SIGNAL 等待触发状态

CONDITION 等待条件状态

PROPAGATE 状态需要向后传播

到此为止,一个线程对于锁的一次竞争才告于段落,结果有两种,要么成功获取到锁(不用进入到AQS队列中),要么,获取失败,被挂起,等待下次唤醒后继续循环尝试获取锁,值得注意的是,AQS的队列为FIFO队列,所以,每次被CPU假唤醒,且当前线程不是出在头节点的位置,也是会被挂起的

非公平锁 加锁:



非公平锁相对于公平锁,只有加锁的环节是不同的

非公平锁首先先去尝试修改AQS状态,如果成功了,当前线程就持有了锁,失败了,再像公平锁一样,进入队列,静静的等待

锁释放:



还是一行代码,再看看sync的内部实现

代码很直观明了,我们再看下是如何唤醒头结点的

至此一个完整的流程就结束了。我们再来总结下整个流程

以公平锁为例

加锁:

    1.尝试获取锁,判断state是否是0,是0的话CAS操作把state改为1,获取到锁
    2.state不是0,判断下是否存在重入锁的情况
    3.如果1和2都失败了,那就要把节点插入到链表中
    4.再把这个线程挂起,挂起的时候还会尝试一次1和2的操作

释放锁:

    1.释放锁
    2.唤醒AQS节点

原文地址:https://www.cnblogs.com/xmzJava/p/8453774.html

时间: 2024-11-14 03:49:59

ReentrantLock 详解的相关文章

Lock的实现之ReentrantLock详解

摘要 Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作: 虽然锁有很多实现,但是都依赖AbstractQueuedSynchronizer类,我们用ReentrantLock进行讲解: ReentrantLock调用过程 ReentrantLock类的API调用都委托给一个内部类 Sync ,而该类继承了 AbstractQueuedSynchronizer类: public class ReentrantLock impleme

ReentrantLock详解 以及与synchronized的区别

ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁 ReentrantLock lock = new ReentrantLock(true); //公平锁 lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果 try { //操作 } finally { lock.unlock(); } Java 理论与实践: JDK 5.0 中更灵活.更具可伸缩性的锁定机制 新的锁定类提高了同步性 -- 但还不能现

JAVA中ReentrantLock详解

前言:本文解决的问题 RentrantLock与Synchronized区别 ReentrantLock特征 ReentrantLock类的方法介绍 1.什么是ReentrantLock 1.1ReentrantLock 与Synchronized区别 在面试中询问ReentrantLock与Synchronized区别时,一般回答都是 ReentrantLock ReentrantLock是JDK方法,需要手动声明上锁和释放锁,因此语法相对复杂些:如果忘记释放锁容易导致死锁 Reentrant

ConcurrentHashMap详解

ConcurrentHashMap详解 注:该文章主要讲的是JDK1.6中ConcurrentHashMap的实现,JDK1.8中ConcurrentHashMap的实现由不同的机制,详解可看:ConcurrentHashMap总结 1 概述 public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable { Concu

java多线程详解

转自:线程间通信.等待唤醒机制.生产者消费者问题(Lock,Condition).停止线程和守护线程.线程优先级 1  线程间通信 1.1  线程间通信 其实就是多个线程在操作同一个资源,但是操作的动作不同. 比如一个线程给一个变量赋值,而另一个线程打印这个变量. 1.2  等待唤醒机制 wait():将线程等待,释放了CPU执行权,同时将线程对象存储到线程池中. notify():唤醒线程池中一个等待的线程,若线程池有多个等待的线程,则任意唤醒一个. notifyAll():唤醒线程池中,所有

Java并发编程之---Lock框架详解

Java 并发开发:Lock 框架详解 摘要: 我们已经知道,synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等.Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题.本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念. 一

Java并发---- Executor并发框架--ThreadToolExecutor类详解(execute方法)

1.构造方法 请参考上篇文章:http://blog.csdn.net/ochangwen/article/details/53044733 2.源码详解 线程池内部有一些状态,先来了解下这些状态的机制.以下用代码注释的方式来解释其中的含义. /* 这个是用一个int来表示workerCount和runState的,其中runState占int的高3位, 其它29位为workerCount的值. workerCount:当前活动的线程数: runState:线程池的当前状态. 用AtomicIn

Java多线程详解(二)

评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡之后,后台线程没有了服务对象,不久就会自动死亡,不再复活.利用setDaemon方法可以把一个线程设置为后台线程,但必须在线程启动之前调用. 例如 : /* * @author [email protected] */ public class DaemonThread extends Thread { pu

Java 多线程详解(三)------线程的同步

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程:http://www.cnblogs.com/ysocean/p/6883491.html 介绍完如何创建进程以及线程了,那么我们接着来看一个实例: 利用多线程模拟 3 个窗口卖票 第一种方法:继承 Thread 类 创建窗口类 TicketSell package com.ys.thread; p