Lock是一个接口,通常会用ReentrantLock(可重入锁)来实现这个接口。

独占式获取锁

1.lock()

ReentrantLock lock=new ReentrantLock();
lock.lock();

当获取锁时通常会调用ReentrantLock的lock()方法。而lock()方法在ReentrantLock是一个抽象方法,默认情况下ReentrantLock是一个非公平锁,
lock()方法具体实现在ReentrantLock的静态内部类NonfairSync中,源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {abstract static class Sync extends AbstractQueuedSynchronizer {abstract void lock();}static final class NonfairSync extends Sync {final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}
}

这里的lock()方法先通过CAS将state的值从0变为1(ReentrantLock用state表示“持有锁的线程已经重复获取该锁的次数”):当state实际值等于预期值0时,表示当前没有线程持有该锁,更新state值为1,将ExclusiveOwnerThread的值为当前线程(Exclusive是独占的意思,ReentrantLock用exclusiveOwnerThread表示“持有锁的线程”),那么该线程获取锁成功。如果CAS失败。说明state实际值并不是0,也就是说已经有线程持有该锁,此时执行acquire(1)再次请求锁。

2.acquire()

调用acquire():会调用ReentrantLock的静态内部AbstractQueuedSynchronizer的acquire方法()。看源码:

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {/*** 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();}
}

从注释我们可以了解到acqiure的作用:我理解的是acquire请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果tryAcquire()再次获取锁成功就直接退出,否则线程进入阻塞- - -唤醒2种状态切换中,直到tryAcquire成功。

可以看到if语句是&&,那么先看tryAcquire()方法。
注:以下方法没有特殊表明都在静态内部类AQS中。
3.tryAcquire()

acquire失败后再次用tryAcquire()获取同步状态。

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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

nonfairTryAcquire()再次获取锁,先判断state是否为0,如果为0即没有线程获取该锁,通过CAS该线程获取到锁。如果state不为0,判断当前线程是否是getExclusiveOwnerThread即持有锁线程,是的话,就将state++,也就是可重入的体现。否则再次获取同步状态失败。
当state=0通过CAS保证同步,即同一个时刻只有一个线程可以获取到锁,但是如果当前线程是持有锁线程并没有保证同步是因为线程可重入是再次获取锁才触发的操作,当前线程拥有该锁,所以对ReentrantLock的属性操作是不用加锁的。

当tryAcquire()再次获取同步状态失败后,会执行addWaiter()方法。

4.addWaiter()

addWaiter()向同步队列中添加一个特定模式(独占式、共享式)的结点。

看源码:

 /*** 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 failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}

这个方法的注释:创建一个为当前线程的节点入队,Node.EXCLUSIVE 是独占锁,Node.SHARED是共享锁。返回所创建的结点。
方法实现思路:创建一个当前线程的指定模式(独占式、共享式)结点后,由于同步队列是具有头尾结点的双向链表,找到尾结点pred,如果pred不为空,使用CAS将当前结点尾插到同步队列中,CAS尾插成功,返回当前Node结点。
如果尾结点不存在或者尾插失败(尾插失败是因为:假如多个线程都需要在同步队列尾插,那么只有一个线程结点会成功,其余都会失败)执行enq()方法。
5.enq()

当前队列为空或者CAS尾插失败调enq()方法来初始化队列或不断尾插。

源码如下:

/*** Inserts node into queue, initializing if necessary. See picture above.* @param node the node to insert* @return node's predecessor*/private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

for循环是死循环,也就是说还有将这个节点尾插成功才会退出for循环。如果队列为空,创建初始化同步队列,头尾结点指向匿名结点。如果尾结点存在,不断CAS将当前结点尾插到同步队列中。
当前线程结点插入同步队列后,调用acquireQueued()排队获取同步状态。

6.acquireQueued()

当前线程结点入队列后,调用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);//删除原来头结点//将原来头结点的next置空,将头结点指向该结点时,该节点的前驱为nullp.next = null; // help GC  failed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

获取到该结点的前驱,如果前驱结点是头结点并且此时可以获取同步状态,那么该线程结点就成功获取到锁,将该线程结点置空并设置为头结点。(因为此时已经获取到锁,可以把该线程释放)

//设置为头结点,该结点置空
private void setHead(Node node) {head = node;node.thread = null;node.prev = null;}

否则就执行shouldParkAfterFailedAcquire()。

7.shouldParkAfterFailedAcquire()

不断重试,直到前驱结点状态为-1,因为只有这样,前驱获取到锁后,释放同步状态后会通知当前结点,使当前结点继续运行。

/*** Checks and updates status for a node that failed to acquire.* Returns true if thread should block. This is the main signal* control in all acquire loops.  Requires that pred == node.prev.** @param pred node's predecessor holding status* @param node the node* @return {@code true} if thread should block*/private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//获取前驱结点的结点状态int ws = pred.waitStatus;//如果前驱结点状态为SIGNAL(-1),直接返回trueif (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;//当前结点状态大于0,只能是取消状态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.*///前驱结点状态不是取消状态时,将前驱结点状态置为-1,表示后继结点应处于阻塞状态compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

当该前驱结点不是SIGNAL状态时,返回false,进入acquireQueued中的for循环。假设node的前驱结点不是头结点或者获取锁失败,则会再次进入shouldParkAfterFailedAcquire()。上一轮循环中,已经将pred.waitStatus设置为SIGNAL=-1,则会进入第一个判断条件。直接返回true,表示应该将当前结点阻塞,进入parkAndCheckInterrupt()方法。

8.parkAndCheckInterrupt()

前驱结点状态为-1,表示需要将当前结点阻塞。

private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

调用 LockSupport.park(this)将当前线程阻塞,当前驱线程结点获取锁并且将锁释放后,唤醒该结点,唤醒后再次进入第6步acquireQueued的for循环获取同步状态。

现在返回到第6步acquireQueued()的finally代码块:

finally {if (failed)cancelAcquire(node);}

如果出现异常或者出现中断,就会执行finally的取消线程的请求操作,核心代码是node.waitStatus = Node.CANCELLED;将线程的状态改为CANCELLED。

acquireQueued(final Node node, int arg):
进入同步队列后排队获取同步状态。

  • 如果当前线程结点的前驱结点为头结点并且能成功获取同步状态,那么当前线程获取锁成功,方法退出。
  • 如果获取锁失败,先不断自旋将前驱结点状态设为SIGNAL,而后调用LockSupport.park()方法将当前线程阻塞来等待唤醒。

    整个过程用一张图表示为:

独占式释放锁

1.unlock( )

调Lock接口的实现子类ReentrantLock的unlock( )方法。

public void unlock() {sync.release(1);}

2.release( )

通过注释可以理解到:释放独占锁,如果tryRelease成功返回true,就会唤醒阻塞等待的线程。否则,释放锁失败。

再调AQS的模板方法release( ),看源码:

     /*** Releases in exclusive mode.  Implemented by unblocking one or* more threads if {@link #tryRelease} returns true.* This method can be used to implement method {@link Lock#unlock}.** @param arg the release argument.  This value is conveyed to*        {@link #tryRelease} but is otherwise uninterpreted and*        can represent anything you like.* @return the value returned from {@link #tryRelease}*/
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

用tryRelease( )方法来释放锁,如果释放成功,先判断头结点是否有效,最后用unparkSuccessor( )启动后续等待的线程。

2.tryRelease( )

protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

tryRelease( )方法先获取state减去要释放的一次,然后判断当前线程是否和持有锁线程一样,如果不一致会抛异常(都没有锁,要释放什么锁呢~ ~),如果一致,再判断state值,只有当值state值为0的时候,才将锁持有者置空,否则说明是重入锁,需要多次释放直至state为空。并且要重新更新state值。

3.unparkSuccessor()

unparkSuccessor():唤醒同步队列中最前边的有效线程。

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);/** 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)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);}

unparkSuccessor():启动后续线程,先将头结点的状态置空,并且获取头结点的next结点。如果next为空或者是取消线程,则从同步队列的尾部向前寻找有效结点,依次更新有效结点。取到同步队列中第一个有效结点后, 通过LockSupport.unpark(s.thread)将该线程唤醒。此时再和acquireQueued()获取同步状态联系起来,假如被唤醒的线程为S,线程S进入 if (p == head && tryAcquire(arg))来获取同步状态。

独占式获取锁和独占式释放锁总结
1.独占式:一个线程在获取锁或者释放锁时会阻塞其他线程。
2.独占式获取锁:线程获取锁失败后,会调用addWaiter( )将该线程封装为结点尾插到同步队列。addWaiter( )中的enq( )完成同步队列的初始化和CAS尾插失败后不断尾插的操作。
3.入队之后排队获取锁的核心方法:acquireQueued( ),结点排队获取锁是一个自旋过程。当且仅当当前结点的前驱结点为头结点并且成功获取同步状态,结点出队并且该结点的线程获取到锁。否则,不断自旋将前驱结点的状态设置为SIGNAL而后调用LockSupport.park( )将该线程阻塞。
4.释放锁时会唤醒同步队列里第一个有效线程结点。(该结点不为空而且线程状态不为取消)

Lock锁和synchronized锁相比独有的特性有:

1.获取锁时响应中断
acquireInterruptibly(int arg);

public final void acquireInterruptibly(int arg) throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg)) //再次获取同步状态失败doAcquireInterruptibly(arg);}private void doAcquireInterruptibly(int arg) throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE); //将该结点尾插到同步队列boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return;}//不断自旋直至将前驱结点状态设置为SIGNAL,然后阻塞当前线程if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//当前线程被阻塞后,会抛异常throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

获取锁响应中断和acquire( )原理几乎一样,唯一区别在于获取锁响应中断的parkAndCheckInterrupt( )返回true时即该线程阻塞时被中断,抛中断异常后线程退出,不会执行后面语句。

2.超时等待获取锁

tryAcquireNanos(int arg, long nanosTimeout) ;
在获取锁 响应中断的基础上可以超时等待。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)   throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);}private boolean doAcquireNanos(int arg, long nanosTimeout)  throws InterruptedException {//如果限时时间小于0直接返回falseif (nanosTimeout <= 0L)return false;//计算出等待线程截止时间:当前时间+等待时间final long deadline = System.nanoTime() + nanosTimeout;//因为tryacquire()失败,所以讲当前线程结点尾插到同步队列final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {//不断自旋将前驱结点状态设置为SIGNALfor (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return true;}//如果超过截止时间,线程不再等待,获取锁失败nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L)return false;if (shouldParkAfterFailedAcquire(p, node) &&nanosTimeout > spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);if (Thread.interrupted())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

超时获取锁和响应中断获取锁的原理基本相同,唯一区别在于获取锁失败后,增加一个时间处理:如果当前时间超过截止时间,线程不再等待,而是直接返回false,即获取锁失败,否则将线程阻塞在同步队列排队获取锁。

超时获取锁tryAcquireNanos( )会返回的情况:
1.到截止时间,线程仍未获取到锁,不再等待直接返回false,即线程获取锁失败;
2.当前线程在超时时间内被中断,抛中断异常后,线程退出。
3.在截止时间内,线程获取到锁,返回true。

深入理解acquire和release原理源码及lock独有特性acquireInterruptibly和tryAcquireNanos相关推荐

  1. 一步步透彻理解Lock的Acquire和Release原理源码

    Java中已知的锁有两种,一种是synchronized,另一种是Lock:这两种的基本原理在之前的文章中已经做了分析: 深入理解Synchronized实现原理 java AQS的实现原理 这次我们 ...

  2. PCL 实现 SAC_IA 算法原理源码解析

    PCL 实现 SAC_IA 算法原理源码解析 采样一致性算法(SAC_IA)用于点云粗配准中,配准效果还不错,PCL 中也实现了该算法,本文深入 PCL 源码,学习一下 SAC_IA 算法在 PCL ...

  3. 动态代理原理源码分析

    看了这篇文章非常不错转载:https://www.jianshu.com/p/4e14dd223897 Java设计模式(14)----------动态代理原理源码分析 上篇文章<Java设计模 ...

  4. Linux内核 eBPF基础:kprobe原理源码分析:源码分析

    Linux内核 eBPF基础 kprobe原理源码分析:源码分析 荣涛 2021年5月11日 在 <Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用>中已经介绍了kp ...

  5. Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例

    Linux内核 eBPF基础 kprobe原理源码分析:基本介绍与使用示例 荣涛 2021年5月11日 kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术. 利用kpro ...

  6. Linux内核 eBPF基础:Tracepoint原理源码分析

    Linux内核 eBPF基础 Tracepoint原理源码分析 荣涛 2021年5月10日 1. 基本原理 需要注意的几点: 本文将从sched_switch相关的tracepoint展开: 关于st ...

  7. 小天带你轻松解决Mybatis延迟加载原理源码问题

    Mybatis延迟加载原理源码解析 Mybatis基本结构图 由上图可以知道MyBatis延迟加载主要使⽤:JavassistProxyFactory,CgliProxyFactoryb实现类.这两种 ...

  8. 深入解析SpringBoot核心运行原理和运作原理源码

    SpringBoot核心运行原理 Spring Boot 最核心的功能就是自动配置,第 1 章中我们已经提到,功能的实现都是基于"约定优于配置"的原则.那么 Spring Boot ...

  9. Java Review - 并发编程_ 信号量Semaphore原理源码剖析

    文章目录 概述 小Demo 类关系概述 核心方法源码解读 void acquire() 非公平策略NonfairSync类的`tryAcquireShared`方法 公平策略`FairSync`类的` ...

最新文章

  1. 全局变量在主函数调用过程中被中断修改的问题
  2. 5GS 协议栈 — PFCP 协议 — PDR 报文检测规则
  3. instr 函数从后往前计数 instr(spell,' ',-1)
  4. 【OpenCV学习】OpenMP并行化实例
  5. 通过smack client + openfire server 实现 peer to peer communication
  6. jedis jedispool Redistemplate
  7. 三条中线分的六个三角形_初中数学——与三角形有关的线段
  8. php自动打印小票_服装店专用小票机自带进销存
  9. APP可临摹分层模板素材|可改善您的登录设计
  10. 关于.net中值类型的方法调用
  11. nlp-tutorial代码注释1-2,词向量、Word2Vec、Skip-gram简述
  12. Java八大算法:归并排序
  13. Linux系统如何测试无线网卡的信号强度,如何用wifi-linux检测AP信号强度
  14. web开发 省市县三级联动
  15. 32位系统支持多大内存 Windows32位/64位系统最大支持内存详解
  16. 鼠标右键转圈圈_【鼠标右键一直在转圈圈】鼠标右键一直在闪_鼠标一直在转圈圈...
  17. Thingworx- 创建一个事物
  18. NOI OJ 1.3 11:计算浮点数相除的余数 C语言
  19. 在计算机上如何连接网络,详细教您如何在计算机上设置宽带连接
  20. Aspose.Words模板创建Word【一】

热门文章

  1. Maven快速理解使用
  2. 群晖nas(DS423+)和百度云盘互相自动备份
  3. Wannafly挑战赛26-B 冥土追魂(贪心?思维?模拟?)
  4. 概率统计·概率论的基本概念【事件独立性、随机变量】
  5. 如何收取google adsense广告费?招行一卡通电汇设置指南
  6. 2010年用最少的钱玩转张家界
  7. 传奇脚本:#AutoRun NPC SEC 参数说明
  8. 工行银企互联接入详解(3)--启动NC
  9. DNF手游多开搬砖赚RMB攻略
  10. 如何打开电脑的服务选项