- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
。
JUC是Java中一个包 java.util.concurrent 。在这个包下,基本存放了Java中一些有关并发的类,包括并发工具,并发集合,锁等.
AQS(抽象队列同步器)是JUC下的一个基础类,大多数的并发工具都是基于AQS实现的.
AQS本质并没有实现太多的业务功能,只是对外提供了 三点核心 内容,来帮助实现其他的并发内容.
三点核心内容:
ReentrantLock就是基于AQS实现的。ReentrantLock类中维护这个一个内部抽象类Sync,他继承了AQS类。ReentrantLock的lock和unlock方法就是调用的Sync的方法.
AQS流程(简述) 1. 当new了一个ReentrantLock时,AQS默认state值为0, head 和 tail 都为null; 2. A线程执行lock方法,获取锁资源。 3. A线程将state通过cas操作从0改为1,代表获取锁资源成功。 4. B线程要获取锁资源时,锁资源被A线程持有。 5. B线程获取锁资源失败,需要添加到双向链表中排队。 6. 挂起B线程,等待A线程释放锁资源,再唤醒挂起的B线程。 7. A线程释放锁资源,将state从1改为0,再唤醒head.next节点。 8. B线程就可以重新尝试获取锁资源。 注: 修改AQS双向链表时要保证一个私有属性变化和两个共有属性变化,只需要让tail变化保证原子性即可。不能先改tail(会破坏双向链表) 。
ReentrantLock中的lock方法实际是执行的Sync的lock方法.
Sync是一个抽象类,继承了AQS 。
Sync有两个子类实现:
Sync的lock方法实现:
// 非公平锁 final void lock() { // CAS操作,尝试将state从0改为1 // 成功就拿到锁资源, 失败执行acquire方法 if (compareAndSetState(0, 1)) // 成功就设置互斥锁的为当前线程拥有 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // 公平锁 final void lock() { acquire(1); }
如果CAS操作没有成功,需要执行acquire 方法走后续 。
acquire方法是AQS提供的,公平和非公平都是走的这个方法 。
public final void acquire(int arg) { // 1. tryAcquire方法: 再次尝试拿锁 // 2. addWaiter方法: 没有获取到锁资源,去排队 // 3. acquireQueued方法:挂起线程和后续被唤醒继续获取锁资源的逻辑 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果这个过程中出现中断,在整个过程结束后再自我中断 selfInterrupt(); }
在AQS中tryAcquire是没有具体实现逻辑的,AQS直接在tryAcquire方法中抛出异常 。
在公平锁和非公平锁中有自己的实现.
// 非公平锁 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // 非公平锁再次尝试拿锁 (注:该方法属于Sync类中) final boolean nonfairTryAcquire(int acquires) { // 获取当前线程对象 final Thread current = Thread.currentThread(); // 获取state状态 int c = getState(); // state是不是没有线程持有锁资源,可以尝试获取锁 if (c == 0) { // 再次CAS操作尝试修改state状态从0改为1 if (compareAndSetState(0, acquires)) { // 成功就设置互斥锁的为当前线程拥有 setExclusiveOwnerThread(current); return true; } } // 锁资源是否被当前线程所持有 (可重入锁) else if (current == getExclusiveOwnerThread()) { // 持有锁资源为当前, 则对state + 1 int nextc = c + acquires; // 健壮性判断 if (nextc < 0) // overflow // 超过最大锁重入次数会抛异常(几率很小,理论上存在) throw new Error("Maximum lock count exceeded"); // 设置state状态,代表锁重入成功 setState(nextc); return true; } return false; }
// 公平锁 protected final boolean tryAcquire(int acquires) { // 获取当前线程对象 final Thread current = Thread.currentThread(); // 获取state状态 int c = getState(); // state是不是没有线程持有锁资源 if (c == 0) { // 当前锁资源没有被其他线程持有 // hasQueuedPredecessors方法: 锁资源没有被持有,进入队列排队 // 排队规则: // 1. 检查队列没有线程排队,抢锁。 // 2. 检查队列有线程排队,查看当前线程是否排在第一位,如果是抢锁,否则入队列(注:该方法只是判断,没有真正入队列) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 再次CAS操作尝试, 成功就设置互斥锁的为当前线程拥有 setExclusiveOwnerThread(current); return true; } } // 锁资源是否被当前线程所持有 (可重入锁) else if (current == getExclusiveOwnerThread()) { // 持有锁资源为当前, 则对state + 1 int nextc = c + acquires; // 健壮性判断 if (nextc < 0) // 超过最大锁重入次数会抛异常(几率很小,理论上存在) throw new Error("Maximum lock count exceeded"); // 设置state状态,代表锁重入成功 setState(nextc); return true; } return false; }
addWaiter方法,就是将当前线程封装为Node对象,并且插入到AQS的双向链表.
// 线程入队列排队 private Node addWaiter(Node mode) { // 将当前对象封装为Node对象 // Node.EXCLUSIVE 表示互斥 Node.SHARED 表示共享 Node node = new Node(Thread.currentThread(), mode); // 获取tail节点 Node pred = tail; // 判断双向链表队列有没有初始化 if (pred != null) { // 将当前线程封装的Node节点prev属性指向tail尾节点 node.prev = pred; // 通过CAS操作设置当前线程封装的Node节点为尾节点 if (compareAndSetTail(pred, node)) { // 成功则将上一个尾节点的next属性指向当前线程封装的Node节点 pred.next = node; return node; } } // 没有初始化head 和 tail 都等于null // enq方法: 插入双向链表和初始化双向链表 enq(node); // 完成节点插入 return node; } // 插入双向链表和初始化双向链表 private Node enq(final Node node) { // 死循环 for (;;) { // 获取当前tail节点 Node t = tail; // 判断尾节点是否初始 if (t == null) { // Must initialize // 通过CAS操作初始化初始化一个虚拟的Node节点,赋给head节点 if (compareAndSetHead(new Node())) tail = head; } else { // 完成当前线程Node节点加入AQS双向链表的过程 // 当前线程封装的Node的上一个prev属性指向tail节点 // 流程: 1. prev(私有) ---> 2. tail(共有) ---> 3. next (共有) node.prev = t; // 通过CAS操作修改tail尾节点指向当前线程封装的Node if (compareAndSetTail(t, node)) { // 将当前线程封装的Node节点赋给上一个Node的下一个next属性 t.next = node; return t; } } } }
acquireQueued方法主要就是线程挂起以及重新尝试获取锁资源的地方 。
重新获取锁资源主要有两种情况:
// 当前线程Node添加到AQS队列后续操作 final boolean acquireQueued(final Node node, int arg) { // 标记,记录拿锁状态 失败 boolean failed = true; try { // 中断状态 boolean interrupted = false; // 死循环 for (;;) { // 获取当前节点的上一个节点 prev final Node p = node.predecessor(); // 判断当前节点是否是head,是则代表当前节点排在第一位 // 如果是第一位,执行tryAcquire方法尝试拿锁 if (p == head && tryAcquire(arg)) { // 都成功,代表拿到锁资源 // 将当前线程Node设置为head节点,同时将Node的thread 和 prev属性设置为null setHead(node); // 将上一个head的next属性设置为null,等待GC回收 p.next = null; // help GC // 拿锁状态 成功 failed = false; // 返回中断状态 return interrupted; } // 没有获取到锁 --- 尝试挂起线程 // shouldParkAfterFailedAcquire方法: 挂起线程前的准备 // parkAndCheckInterrupt方法: 挂起当前线程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 设置中断线程状态 interrupted = true; } } finally { // 取消节点 if (failed) cancelAcquire(node); } } // 检查并更新无法获取锁节点的状态 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取上一个节点的ws状态 /** * SIGNAL(-1) 表示当前节点释放锁的时候,需要唤醒下一个节点。或者说后继节点在等待当前节点唤醒,后继节点入队时候,会将前驱节点更新给signal。 * CANCELLED(1) 表示当前节点已取消调度。当timeout或者中断情况下,会触发变更为此状态,进入该状态后的节点不再变化。 * CONDITION(-2) 当其他线程调用了condition的signal方法后,condition状态的节点会从等待队列转移到同步队列中,等待获取同步锁。 * PROPAGATE(-3) 表示共享模式下,前驱节点不仅会唤醒其后继节点,同时也可能唤醒后继的后继节点。 * 默认(0) 新节点入队时候的默认状态。 */ int ws = pred.waitStatus; // 判断上个节点ws状态是否是 -1, 是则挂起 if (ws == Node.SIGNAL) return true; if (ws > 0) { /** * 判断上个节点是否是取消或者其他状态。 * 向前找到不是取消状态的节点,修改ws状态。 * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链, * 稍后就会被GC回收,这个操作实际是把队列中的cancelled节点剔除掉。 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 如果前驱节点正常,那就把上一个节点的状态通过CAS的方式设置成-1 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } // 挂起当前线程 private final boolean parkAndCheckInterrupt() { // 挂起当前线程 LockSupport.park(this); // 返回中断标志 return Thread.interrupted(); }
// 互斥锁模式 解锁 public final boolean release(int arg) { // 尝试是否可以解锁 if (tryRelease(arg)) { Node h = head; // 判断双链表是否存在线程排队 if (h != null && h.waitStatus != 0) // 唤醒后续线程 unparkSuccessor(h); return true; } return false; } // 尝试是否可以解锁 protected final boolean tryRelease(int releases) { // 锁状态 = 状态 - 1 int c = getState() - releases; // 判断锁是是否是当前线程持有 if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程没有持有抛出异常 throw new IllegalMonitorStateException(); boolean free = false; // 当前锁状态变为0,则清空锁归属线程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 设置锁状态为0 setState(c); return free; } // 唤醒线程 private void unparkSuccessor(Node node) { // 获取头节点的状态 int ws = node.waitStatus; if (ws < 0) // 通过CAS将头节点的状态设置为初始状态 compareAndSetWaitStatus(node, ws, 0); // 后继节点 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); }
。
以上仅供参考!! 。
最后此篇关于Java-ReentrantLock锁分析的文章就讲到这里了,如果你想了解更多关于Java-ReentrantLock锁分析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
ReentrantLock 在创建 Lock 对象时提供 boolean fair 标志。 公平:真实 线程根据其等待的时间来访问临界区。 公平:假 没有为线程提供临界区的具体策略。 下面是我的代码:
我是 Java 新手。我只是在尝试线程,我想创建类似线程池的东西(如果这实际上是我正在做的事情......)。 基本上,我有一个 while 循环,它会触发线程,直到仍然有任务要执行&&,同时最大并发
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界. 这篇CFSDN的博客文章详解java并发之重入锁-ReentrantLock由作者收集整理,如果
有谁知道方法acquire()和release()(java.util.concurrent.Semaphore)和await () 和 signal (new ReentrantLock().new
我一直在做一个关于 Java 多线程的学校作业。我遇到的一个任务是,我们需要在不同的组中创建多个线程,一旦每个组中有 4 个线程,只有这样才能释放它们协同工作,否则它们必须被搁置/等待。例如: 线程
我试图了解 Java 中 ReentrantLock 的内部工作原理。 我创建了一个示例,例如:- package com.thread.trylock; import java.util.concu
问题出在哪里? ReentrantLock 未显示预期结果。两个线程同时执行,而不是等待一个线程。 class MyThread2 extends Thread{ String name;
我正在为 Android 2.2 开发,对 ReentrantLocks 的工作原理有点困惑。以下代码是否会抛出 IllegalMonitorStateException?我问是因为我看不出它是如何做
我们的应用程序在 WebLogic 12c 中运行,正在从队列系统中检索消息,我们从中检索消息的队列配置为 FIFO。我们使用 Spring 来配置检索功能,并且容器 (org.springframe
我使用线程(等待和通知)功能创建了一个生产者消费者程序。代码是—— /** * Message.java ( Common object ) */ package threads; import
假设我有一个服务器有多个线程共享一个数据实例的引用。快速例如, edit1:为可读性更新 public void main() { Data data = new Data(); Reentran
我不明白为什么代码不能正常工作。问题是 ReentrantLock 没有锁定 ThreadClass.run() 中的方法调用 Resource-class哪些方法被假定为在ThreadClass中被
重入锁 void a() { lock.lock(); //got intrinsic lock System.out.println(Thread.currentThread(
我正在尝试在多线程上实现可重入锁,但由于某种原因,同一个线程解锁然后再次锁定导致始终运行相同的线程,因此运行相同的操作。 下面是线程产生的代码 IntStream.range(0,(NUMBER_OF
我有两个方法都由不同的线程运行,其中一个方法我不想在另一个方法被调用时运行。这就是我选择使用锁的原因 @Override public synchronized void doSomething(in
下面是线程被锁定之前和线程解锁之后的所有信息。我正在使用 ReentrantLock 的 lock() 和 unlock() 方法。然而,虽然某个进程已经解锁了该锁,但另一个进程却无法锁定同一个锁。
ReentrantLock 允许线程多次锁定某个资源, 这在执行/效率/功能方面有何好处? 请参阅此链接,https://www.geeksforgeeks.org/reentrant-lock-ja
当我在互联网上阅读一些并发代码示例时,我发现了这个(2个银行帐户之间的转账操作): class Account { double balance; int id; pub
我有一个 ReentrantLock,一堆操作正在锁定它,它是使用 new ReentrantLock(true) 公平创建的。有没有办法让线程“闯入”锁并在锁释放后但在任何其他线程之前获取它? 我考
我有一个带有静态变量的类,多个线程都会有这个类的实例。 我关心的静态变量是一个Thread,它将从队列中弹出一条消息并通过电子邮件发送,直到队列为空。每次将消息添加到队列时,我都会检查线程是否还活着。
我是一名优秀的程序员,十分优秀!