- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
是它,是它,就是它,并发包的基石; 。
闲来不卷,随便聊一点.
一般情况下,大家系统中至少也是JDK8了,那想必对于JDK5加入的一系列功能并不陌生吧。那时候重点加入了 java.util.concurrent 并发包,我们简称为JUC。JUC下提供了很多并发编程实用的工具类,比如并发锁lock、原子操作atomic、线程池操作Executor等等。下面,我对JUC做了整理,大致分为下面几点:
基于JDK8,今天重点来聊下JUC并发包下的一个类, AbstractQueuedSynchronizer .
首先,浅显的从名字上看, 抽象的队列同步器 ;实际上,这名字也跟它的作用如出一辙。 抽象 ,即需要被继承; 队列同步器 ,其内部维护了一个队列,供线程入队等待;最终实现多个线程访问共享资源的功能.
进入 AbstractQueuedSynchronizer 内部,需要掌握三个重要的属性:
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
我们调试AQS的源码,必须寻找一个源码调试的切入点,我这里用我们并发编程常用的Lock锁作为调试AQS的切入点,因为这是解决线程安全问题常用的手段之一.
AQS的源码调试,从 Lock 接口出发,JDK源码定义如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
从源码中看到, Lock 是一个接口,所以该接口会有一些实现类,其中有一个实现类 ReentrantLock ,可重入锁,想必大家都不会陌生.
通过跟踪源码可以看到,ReentrantLock#lock内部实现貌似比较简单,只有简短的一行代码 。
public void lock() {
sync.lock();
}
其实内部是维护了一个 Sync 的抽象类,调用的是 Sync 的lock()方法.
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
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) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
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;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
// ...
}
可以看到, Sync 也是个抽象类,它有两个实现类: NonfairSync 和 FairSync ,这里其实就引出了我们今天的主角, AbstractQueuedSynchronizer , Sync 继承了它.
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
下面我整理了这一系列类的UML图 。
通过类图可知,lock()方法最终调用的是 ReentrantLock 类下,内部类 NonfairSync 或 FairSync 的lock方法;对于这两个类,前者叫非公平锁,后者叫公平锁。通过 ReentrantLock 的构造器可知,默认使用 NonfairSync 类.
public ReentrantLock() {
sync = new NonfairSync();
}
从 NonfairSync 类的lock方法出发,引出第一个AQS下的方法compareAndSetState.
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
从compareAndSetState方法的命名可以发现,就是比较并交换的意思,典型的 CAS无锁 机制.
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
我们可以观察到,这里其实调用的是Unsafe类的compareAndSwapInt方法,传入的expect为0,update为1;意思是如果当前值为0,那我就把值最终更新为1.
Unsafe 这个类下面,发现好多方法都是用 native 这个关键词进行修饰的(也包括compareAndSwapInt方法),用 native 关键词修饰的方法,表示原生的方法;原生方法的实现并不是Java语言,最终实现是C/C++;这并不是本文的讨论范围.
回到AQS的compareAndSetState方法,返回值是boolean类型,true表示值更新为1成功,false表示不成功。这里出现两个分支,成功,走setExclusiveOwnerThread方法;不成功,走acquire方法。咱优先讨论acquire方法.
先来看一下该方法的源码; 。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里的核心是两个方法,tryAcquire方法和acquireQueued方法。首先会调用tryAcquire()方法,看方法命名是尝试获取;实际上这个方法确实在就在做一件事“ 尝试获取资源 ”.
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
不过AQS中的这个方法是 protected 修饰,并没有去实现,仅仅只是预留了方法入口,后期需要由其子类去实现;这里的子类就是上文中的 NonfairSync 类,该类的源码在上文中已经贴出。这段源码其实运用了我们常见的一个设计模式,“ 模板方法模式 ”.
NonfairSync的tryAcquire方法源码如下 。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
这里并没有直接去实现tryAcquire方法,而是调用了 Sync 类下的nonfairTryAcquire方法.
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) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这里有个getState方法,最终返回的是AQS中的state字段,这个字段就是多个线程抢占的共享资源,所以 这个字段很重要 ; volatile 关键字修饰,保证内存的可见性, int 类型,对于ReentrantLock锁而言,当state=0时,表示无锁,当state>0时,表示资源已被线程锁定.
下面分析下这段代码:
tryAcquire方法的判断至此结束,不过最终的走向需要看它的返回值;返回true,表示当前线程抢占到锁,或者当前线程就是抢占锁的线程,直接重入,加锁流程结束;返回false,表示没有抢占到锁,流程继续,这里就引出下个话题, CLH 线程等待队列.
首先咱来看一段源码中的注释 。
The wait queue is a variant of a "CLH" (Craig, Landin, and Hagersten) lock queue. CLH locks are normally used for spinlocks 。
大致意思是:CLH队列是由Craig、Landin、Hagersten这三位老哥名字的首字母叠加在一起命名的,它是一个等待队列,它是一个变种队列,用到了自旋.
这里的信息要抓住三点: 等待队列、变种队列、自旋.
在解析addWaiter方法实现之前,就不得不提到一个内部类 Node ;addWaiter方法的入参是这个类型,所以先来看看这个类。源码如下:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
这里先大致介绍下,每个属性的意思:
另外,Node类还有两个有参构造器: 从作者的注释就能看出来,第一个构造器是在等待队列的时,创建节点使用,第二个构造器是在条件队列时,创建节点使用.
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
其实这段方法是在创建Node对象,Node对象就是组成CLH队列的基础元素.
Node.EXCLUSIVE
,表示独占模式。 总结下addWaiter方法干的事情:
还是先来看下这个方法的源码; 。
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);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
从这个方法看到,又是运用了无限循环,需要分两个步骤去观察:1.当前方法中的判断,自己的上一个节点是否是头部节点(头部节点就是占用资源的节点);2.当前节点正式入队列,并且被挂起.
当前节点的前一个节点是队列头部,意味着当前节点的前一个节点,就是持有资源的节点;当资源被释放,当前节点会去尝试争夺锁资源;如果拿到锁资源,当前节点会被标记为队列头部节点,它的上个节点(老的头部节点)会被置为null,需要被GC及时清除,所以作者在这里添加了一个注释:help GC;下图就是描述了这个流程:
如果当前节点的上一个节点,并不是头部节点;这里就需要用到上述Node类中介绍的各种状态字段了;先来重点介绍下 Node 类中的两个状态属性:
进入的 shouldParkAfterFailedAcquire 这个方法内部,该方法接受两个参数:当前节点前一个节点和当前节点。首先,获取上一个节点的waitStatus属性,然后通过这个属性做如下判断:
这里可以想象成一个排队去食堂打饭的场景,你在低头玩手机前,跟你前面的同学说,我玩会手机,快到了叫我一下;结果你前面的同学嫌队伍长走了(CANCELLED状态),所以你只能继续找他的上一个同学;直到有同学回答你,好的(该同学被标记SIGNAL状态);然后你就低头玩手机,等待回答你“好的”的那个同学叫你.
再来看下 parkAndCheckInterrupt 这个方法 。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
// LockSupport#park
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
其中最终又是这个 Unsafe 类,通过它的原生方法park,去挂起当前线程,这里就不展开赘述了.
下面整理下从lock方法作为切入点,一系列的调用:
之前一直在讲资源“上锁”,那么这个方法就是给资源解锁。这里给出重要的部分源码 。
// AQS中
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// AQS中
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
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);
}
// ReentrantLock中
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;
}
在调用unlock方法去解锁后,最终是调用AQS中的release方法去实现这个解锁功能的;在该方法中,首先会调用ReentrantLock中的tryRelease方法,去做state状态值的递减操作.
在tryRelease方法返回false的时候,release方法并不会做任何操作,直接就结束了,意味着解锁并没有完成; 但是在返回true的时候,具体分以下几部操作:
上面说到了,这个方法主要是用来唤醒线程的,下面还是做一下具体的解析:
Unsafe
类的unpark原生方法去唤醒上述找到的,距离头部节点最近的未处于取消状态下的节点。 通过上面的描述可以发现,资源解锁是相对简单的;它只能被上锁的线程去解锁;通过递减AQS内部维护的state属性值,直到state减为0,表示资源已被解锁;当资源被解锁后,需要通过 Unsafe 的unpark方法,去唤醒CLH队列中,被挂起的第一个节点上的线程.
在2.2中说过,当我们使用无参构造器创建一把“锁”的时候,默认是使用NonfairSync这个内部类,也就是非公平锁;但是在源码中发现 ReentrantLock 还存在一个有参构造器,参数是一个 boolean 类型; 。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
很明显,这种方式就是将选择权交给开发人员,当我们传入true时,就会创建一把“公平锁”。还是一样,先来看下公平锁的内部; 。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
从源码的角度,咱来看下,为什么一个叫“非公平锁”,另一个叫“公平锁”?
其实不难发现, NonfairSync 内部的lock方法,它是一上来就通过cas机制去抢占state公共资源,抢不到才去执行acquire方法实现后续入队列等一系列的操作;而这里 FairSync 的lock方法,它是直接执行acquire方法,执行后续的操作。等于非公平锁,会去多争取一次资源,对于在CLH队列中等待的线程,是“不公平”的.
除了lock方法存在差异之外,在tryAcquire方法中,也存在着不同。 FairSync 类中,会多执行hasQueuedPredecessors方法,它是AQS下的一个公用方法,下面具体看下这个方法; 。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
只有简短的几行,却有很多种可能性,但是整个方法主要功能就是判断当前线程是否需要入队列:返回false,队列为空,不对等待;返回true,队列不是空,去排队等待。下面需要重点讲下这一行代码: return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()),
返回false,情况也有两种:1、h != t** **是false,2、h != t是true,并且 (s = h.next) == null 是false, s.thread != Thread.currentThread()是false.
第一种情况比较简单,意思是头结点和尾节点是同一个,说明队列是空的,不需要排队等待,所以直接返回false.
第二种情况,头尾不是同一个节点,头部节点的下个节点也不是空,并且头部节点的下一个节点就是当前线程。 其实就可以理解为,前面的资源刚释放,正好轮到当前线程来抢占资源,这种情况相对较少.
返回true,有两种情况:1、h != t是true,并且 (s = h.next) == null 是true。2、h != t是true,并且 (s = h.next) == null 是false, s.thread != Thread.currentThread()是true.
1、这里的头尾不是同一个节点是必要满足的条件,保证了队列起码不是空的。然后(s = h.next) == null 满足是true,这里解释起来就必须回顾下enq初始化队列这个方法.
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
从这个方法可知,先是将节点的prev指向前一个节点,然后再通过cas修改尾部标识,最后再将前一个节点的next指向当前节点; 因此AQS,入队操作是非原子性的 .
继续回到判断本身,头部节点拿到锁在执行;中间节点没拿到锁在入队;此时头部节点执行完后释放锁,当前节点尝试不入队拿锁,但是中间线程已经在排队了,但是还没来得及执行t.next = node的操作,导致(s = h.next) == null 满足,所以当前节点必须入队,最终返回true.
2、满足s.thread != Thread.currentThread()的情况,执行到这里,可以明确队列首先不是空,并且h.next != null,也就是头节点之后还有其他节点,最后再判断了下,s.thread != Thread.currentThread为true,也就是头节点的下个节点并不是当前节点,既然如此,那只能乖乖去队列中排队了,所以最终返回true.
想必大家对于并发锁并不陌生了,上文我也是通过 ReentrantLock 这个并发锁为入口,一步步来解析AQS中的实现。所以这里就不用ReentrantLock举例,这里换一个同步工具: CountDownLatch ,它也是基于AQS来实现的.
CountDownLatch 是通过一个计数器来实现的,初始值为线程的数量。每当一个线程完成了自己的任务,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已执行完毕,然后在等待的线程就可以恢复执行任务.
这个其实跟 ReentrantLock 思路差不多,一个是state初始值就是0,通过“上锁”一步步叠加这个值;一个是state让使用者自己设定初始值,通过线程调用,一步步递减这个值.
CountDownLatch 具体的运用情况如下:1、一个主线程中,需要开启多个子线程,并且要在多个子线程执行完毕后,主线程才能继续往下执行。2、通过多个线程一起执行,提高执行的效率.
下面,通过一个真实的业务场景,来进一步了解下 CountDownLatch 这个同步工具,具体是怎么使用的.
现在有这么一个接口,查询用户的详情信息;用户信息由五部分组成:1、用户基本信息;2、用户影像信息;3、用户工商信息;4、用户账户信息;5、用户组织架构信息;按照原本的逻辑是按照顺序1-5这样一步步查询,最后组装用户VO对象,接口返回。但是这里可以用上 CountDownLatch 这个工具类,申请五个线程,分别去查询这五种信息,提高接口效率.
/**
* @author 往事如风
* @version 1.0
* @date 2023/4/11 18:10
* @description:导出报表
*/
@RestController
public class QueryController {
@GetMapping("/query")
public Result download() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 模拟查询数据
List<String> row1 = CollUtil.newArrayList("aa", "bb", "cc", "dd");
List<String> row2 = CollUtil.newArrayList("aa1", "bb1", "cc1", "dd1");
List<String> row3 = CollUtil.newArrayList("aa2", "bb2", "cc2", "dd2");
List<String> row4 = CollUtil.newArrayList("aa3", "bb3", "cc3", "dd3");
List<String> row5 = CollUtil.newArrayList("aa4", "bb4", "cc4", "dd4");
CountDownLatch count = new CountDownLatch(5);
DataQuery d = new DataQuery();
// 开始时间
long start = System.currentTimeMillis();
System.out.println("开始查询数据。。。。");
executorService.execute(() -> {
System.out.println("查询用户基本信息。。。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
d.setBaseInfo(row1);
count.countDown();
});
executorService.execute(() -> {
System.out.println("查询用户影像信息。。。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
d.setImgInfo(row2);
count.countDown();
});
executorService.execute(() -> {
System.out.println("查询用户工商信息。。。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
d.setBusinessInfo(row3);
count.countDown();
});
executorService.execute(() -> {
System.out.println("查询用户账户信息。。。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
d.setAccountInfo(row4);
count.countDown();
});
executorService.execute(() -> {
System.out.println("查询用户组织架构信息。。。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
d.setOrgInfo(row5);
count.countDown();
});
// 阻塞:直到count的值减为0
count.await();
executorService.shutdown();
// 结束时间
long end = System.currentTimeMillis();
System.out.println("查询结束。。。。。");
System.out.println("用时时间:" + (end - start));
return Result.success(d);
}
@Data
class DataQuery {
private List<String> baseInfo;
private List<String> imgInfo;
private List<String> businessInfo;
private List<String> accountInfo;
private List<String> orgInfo;
}
}
/*
控制台输出:
开始查询数据。。。。
查询用户基本信息。。。。。。
查询用户影像信息。。。。。。
查询用户工商信息。。。。。。
查询用户账户信息。。。。。。
查询用户组织架构信息。。。。。。
查询结束。。。。。
用时时间:1017
*/
这段代码做了模拟查询各种用户信息的操作,其中每个线程都暂停1秒,代表在查询这五种数据;最终打印的用时时间是1017ms,说明这五个线程是同时进行的,大大提高了接口的效率.
AQS提供了一个FIFO队列,这里称为CLH队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有: ReentrantLock 、 CountDownLatch 、 Semaphore 等.
AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件.
可以这么说,只要搞懂了AQS,那么J.U.C中绝大部分的api都能轻松掌握.
本文主要提供了从 ReentrantLock 出发,解析了AQS中的各种公用的方法,如果需要知道其他类中怎么去使用AQS中的方法,其实也只需要找到切入点,一步步调试下去即可,不过,我想很多地方都是和 ReentrantLock 中一致的.
最后此篇关于从ReentrantLock角度解析AQS的文章就讲到这里了,如果你想了解更多关于从ReentrantLock角度解析AQS的内容请搜索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,它将从队列中弹出一条消息并通过电子邮件发送,直到队列为空。每次将消息添加到队列时,我都会检查线程是否还活着。
我是一名优秀的程序员,十分优秀!