因为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);
}
分享到:
相关推荐
ReentrantLock 的实现原理基于 AQS(AbstractQueuedSynchronizer),是一个重入锁,允许一个线程反复地获取锁而不会出现自己阻塞自己的情况。 ReentrantLock 的构造方法可以指定锁的类型,包括公平锁和非公平锁。...
5. 可重入锁:ReentrantLock 锁支持可重入锁机制,允许线程多次获得锁。 三、Volatile 原理 1. volatile 关键字:volatile 关键字用于声明变量,可以确保变量的可见性和禁止指令重排。 2. volatile 的应用:...
本文将深入探讨四种关键的并发控制机制:synchronized关键字、ReentrantLock(可重入锁)、volatile关键字以及Atomic类的原理与应用。 ### 1. synchronized关键字 `synchronized`关键字是Java提供的内置锁,用于...
ReentrantLock源码详解--公平锁、非公平锁 ReentrantLock是一种重入锁,实现了...ReentrantLock是一种强大的锁机制,能够满足各种锁相关的需求。其可重入性、公平锁和非公平锁机制使得其在实际编程中使用频率很高。
- **超时机制**:ReentrantLock的tryLock(long time, TimeUnit unit)方法允许设置等待时间,超时未获取到锁则返回false,这实现了锁的可等待性。 6. **总结** 了解ReentrantLock的实现原理,有助于我们在并发...
Java中的显示锁ReentrantLock使用与原理详解 Java中的显示锁ReentrantLock是Java concurrency API中的一种同步机制,用于解决多线程安全问题。ReentrantLock是Java 5中引入的,它是一种可重入锁,允许同一个线程多...
总的来说,ReentrantLock通过AQS和Unsafe实现了高效且灵活的锁机制,不仅支持可重入,还有公平和非公平策略的选择。理解其工作原理有助于我们编写更高效、更安全的多线程代码。在实际开发中,可以根据需求选择使用...
《ReentrantLock源码详解与应用》 ReentrantLock,可重入锁,是Java并发编程中一个重要的锁实现,它提供了比...理解ReentrantLock的源码有助于我们更好地掌握并发编程中的锁机制,以优化并发性能和避免死锁等问题。
内容概要:本文深入探讨了Java中的并发控制机制,重点讲解了ReentrantLock和synchronized的特点及其背后的实现原理。通过对两者的特性进行对比,详细解析了ReentrantLock在灵活性、公平性和中断响应等方面的优点。并...
总结,ReentrantLock通过内部类和AQS实现了高度定制化的锁机制,非公平锁和公平锁的实现方式不同,分别适用于不同的并发场景。理解其工作原理有助于我们在实际开发中更有效地利用并发资源,提高程序性能。
Java中的ReentrantLock是Java并发包(java.util.concurrent.locks)中的一个高级锁,它是可重入的,意味着一个线程可以多次获取同一锁。在深入ReentrantLock之前,我们首先需要了解Java并发编程的基础,特别是Java...
本文将深入探讨Java锁机制,并基于提供的"面向Java锁机制的字节码自动重构框架"来讨论其背后的原理和应用。 在Java中,锁主要分为内置锁(也称为监视器锁)和显式锁。内置锁是通过synchronized关键字实现的,它提供...
Java语言提供了多种锁机制,包括`synchronized`关键字、`ReentrantLock`类以及`ReadWriteLock`接口等。 #### 二、synchronized关键字详解 `synchronized`是Java中一种最基本的锁机制,它可以修饰实例方法、静态...
ReentrantLock,即可重入锁,是Java并发包(java.util.concurrent.locks)中的一个核心组件,它提供了比synchronized更灵活的锁机制。ReentrantLock实现了Lock接口,具备公平锁与非公平锁两种模式,同时支持中断等待...
根据给定文件的信息,我们可以深入理解AQS(AbstractQueuedSynchronizer)独占锁之ReentrantLock的源码分析及其实现原理。这不仅包括ReentrantLock本身的特性,还包括了其背后的AQS框架是如何工作的。 ### 一、管程...
Java中的Locks框架提供了一种比传统的`synchronized`关键字更为强大和灵活的线程同步机制。...了解ReentrantLock的工作原理、用法及其与`synchronized`的区别,将有助于编写出更加高效和健壮的并发程序。
详解 Java 并发之重入锁-ReentrantLock Java 中的并发编程是一种复杂的技术,需要深入了解 Java 的并发机制和锁机制。...ReentrantLock 的实现原理是基于 AQS 框架的,它的锁机制可以分为公平锁和非公平锁两种。
在Java中,主要的锁机制包括synchronized关键字和Lock接口(如ReentrantLock)。这里我们将详细讲解这两种锁以及它们的工作原理。 1. **synchronized关键字** - **synchronized方法**:synchronized修饰的方法会为...
在计算机科学和编程领域,尤其是多线程编程中,锁机制是确保数据一致性与线程安全的关键工具。本文将深入探讨锁的原理、类型以及在实际应用中的使用方法。 一、锁的概述 锁机制是一种同步机制,用于控制对共享资源...
Java中的锁机制是多线程编程中至关重要的一个概念,用于协调多个线程对共享资源的访问。在Java中,有两种主要的锁机制:synchronized和Lock。它们都是用来实现线程同步,防止数据竞争,确保并发环境下的数据一致性。...