因为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);
}
分享到:
相关推荐
这份资源旨在详细讲解 Java 中的 Locks 框架,特别关注 ReentrantLock 的使用和原理。Locks 框架提供了比传统的 synchronized 关键字更强大、更灵活的线程同步机制,而 ReentrantLock 是其中的一种重要实现。 Locks ...
Lock锁,一种线程同步机制,其主要功能是防止多个线程同时访问同一代码块,从而避免因并发问题引发的数据不一致或其他错误。Lock锁的灵活性相比synchronized更高,它支持手动获取和释放锁,能够中断的获取锁以及超时...
可重入性和重入锁: 解释 Lock 接口的可重入性,讲解同一个线程多次获取锁的机制,避免死锁。介绍 ReentrantLock 的实现原理。 Condition 条件变量: 介绍 Lock 接口中的 Condition,它可以实现更复杂的线程等待和...
同步机制:讲解如何使用锁、信号量、原子操作等同步机制来避免竞态条件和死锁。 并发模型:介绍不同的并发模型,如生产者-消费者模型、管道模型、消息传递模型等。 并发工具:介绍并发编程中使用的工具和库,如...
ReentrantLock如何实现可重入性 volatile作用; wait 与 sleep 的有什么不同?回答的要点四个: Thread.sleep()和LockSupport.park()的区别 Object.wait()和LockSupport.park()的区别 线程和线程池 线程池的五种状态...
JUC 包中的同步类基本都是基于 AQS 同步器来实现的,如 ReentrantLock,Semaphore 等。 二、原理 1、AQS 工作机制: 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为...
目录线程安全 Thread Safety重入锁 ReentrantLock读写锁 ReadWriteLock倒计数器 CountDownLatch循环栅栏 CyclicBarrier信号量 Semaphore 线程安全 Thread Safety JMM JMM(Java Memory Model)是一种基于计算机内存...
ReentrantLock 实现原理 ConcurrentHashMap 的实现原理 线程池原理 深入理解线程通信 交替打印奇偶数 JVM Java 运行时内存划分 类加载机制 OOM 分析 垃圾回收 对象的创建与内存分配 你应该知道的 volatile 关键字 ...
ReentrantLock 实现原理 ConcurrentHashMap 的实现原理 线程池原理 深入理解线程通信 交替打印奇偶数 JVM Java 运行时内存划分 类加载机制 OOM 分析 垃圾回收 对象的创建与内存分配 你应该知道的 volatile 关键字 ...
ReentrantLock 实现原理 ConcurrentHashMap 的实现原理 线程池原理 深入理解线程通信 交替打印奇偶数 JVM Java 运行时内存划分 类加载机制 OOM 分析 垃圾回收 对象的创建与内存分配 你应该知道的 volatile 关键字 ...
由浅入深,通过图解和手写代码,讲解Java版的多线程,主要...线程同步+各种锁的原理&手写实现 JDK多线程工具包中,若干种工具的原理和手写实现: ReentrantLock、CountDownLanuh、CyclicBarrier、Semaphore
11.深入理解读写锁ReentrantReadWriteLock 12.详解Condition的await和signal等待通知机制 13.LockSupport工具 14.并发容器之ConcurrentHashMap(JDK 1.8版本) 15.并发容器之ConcurrentLinkedQueue 16.并发容器之...
数据存储原理 Mysql 索引 abc 复合索引 数据库隔离级别 InnoDB 与 MySAIM 区别 Mysql MVCC JVM Java 类加载过程 Java 类加载机制 新生代频繁 gc 如何调整 CMS 垃圾回收器 锁 Lock 与 Sychronized 区别 Redis 分布式...
46、谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解 49、synchronized 和volatile 关键字的区别 51-58题 51、ReentrantLock 、synchronized和volatile比较 53、死锁的四个必要条件? 56、什么是线程池...
48.多线程锁的升级原理是什么? 49.什么是死锁? 50.怎么防止死锁? 51.ThreadLocal 是什么?有哪些使用场景? 52.说一下 synchronized 底层实现原理? 53.synchronized 和 volatile 的区别是什么? 54.synchronized...
1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM .........................
1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM ........................
ReentrantLock详细讲解_.mp4 高并发编程第三阶段27讲 ReadWriteLock&ReentrantReadWriteLock详细讲解_.mp4 高并发编程第三阶段28讲 Condition初步使用,提出几个疑问_.mp4 高并发编程第三阶段29讲 关于...
ReentrantLock详细讲解_.mp4 高并发编程第三阶段27讲 ReadWriteLock&ReentrantReadWriteLock详细讲解_.mp4 高并发编程第三阶段28讲 Condition初步使用,提出几个疑问_.mp4 高并发编程第三阶段29讲 关于...
17.3.2 ReentrantLock锁的具体使用 387 17.3.3 ReadWriteLock接口与ReentrantReadWriteLock类简介 390 17.3.4 ReentrantReadWriteLock读/写锁的具体使用 391 17.4 信号量的使用 393 17.4.1 Semaphore类简介...