并发编程AQS----共享锁
Semaphore
Semaphore构造方法
public Semaphore(int permits) {------permits 表示能同时有多少个线程访问我们的资源
sync = new NonfairSync(permits); -------------默认创建的是非公平锁。
}
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);-------传入的permits做i为了state的值,作为资源总数
}
semaphore.acquire();获取资源,源码实现
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);---------每次申请一次资源
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();-----------------线程无效直接抛异常
if (tryAcquireShared(arg) < 0) --------------------拿不到资源,需要进行入队操作
doAcquireSharedInterruptibly(arg); ---------入队操作
}
final int nonfairTryAcquireShared(int acquires) { --------获取资源的操作
for (;;) {
int available = getState(); --------------拿到现有的资源
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining)) -----------原子操作,多线程情况下会可能失败,所以无线循环自旋下去,直到成功;
return remaining;---------------------如果大于等于0那么就是拿到了资源,如果小于0,那么线程就要进入等待队列
}
}
为什么要用死循环----compareAndSetState这个是cas原子操作,失败之后要循环重复继续操作,直到成功。死循环也就结束了。
private void doAcquireSharedInterruptibly(int arg)-------------线程入队操作
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);---------------注意这里是以共享的方式入队
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) { --------新入队的节点会判断他上一个节点是不是头节点,如果是头节点会再次尝试获取资源,
int r = tryAcquireShared(arg);
if (r >= 0) { -------------如果获取到资源,那么这个阻塞队列就要清空了,里面没有在等待的线程了。
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && ----------如果获取不到资源,那么就要线程阻塞了
parkAndCheckInterrupt()) -----------parkAndCheckInterrupt这个方法会将线程阻塞(挂起),线程都阻塞了,这个死循环就不会执行了,这也就是为什么juc源码写了很多
死循环都没问题地原因,我们可以借鉴。当线程被唤醒之后又开始这个死循环,尝试拿资源(非公平锁有可能拿不到),
拿不到再次被阻塞挂起。
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { ---------------判断线程能否被正常阻塞
int ws = pred.waitStatus;
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) { -----------如果上一个节点是无效的,那就查找上上个节点是不是有效的,直到找到那个有效的节点,然后将该节点插入到那个有效节点后面,中间的无效节点从链表中删除,后面的节点要找前面
的节点这也就说明了为什么我们地等待队列要设计成双链表,不光有next。next这种找后驱节点地操作还有pre .pre这样前驱节点。所以需要双链表。
/*
* 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);
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases; -----------获取当前的资源然后给资源加回去
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) -----------------CAS算法还资源,死循环,直到成功还回去,死循环结束。
return true;
}
}
资源还回去之后执行doReleaseShared方法唤醒其他线程抢资源
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) { --------发现阻塞队列有阻塞线程
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); ---------跳过头节点,唤醒下一个节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
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; ------循环找到waitStatus<0能唤醒的节点调用unpark方法唤醒线程。
}
if (s != null)
LockSupport.unpark(s.thread);
}
CountDownLatch是什么?
CyclicBarrier
并发编程AQS----共享锁的更多相关文章
- Java并发编程--AQS
概述 抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是用来构建锁或者其他同步组件的基础框架,它使用一个整型的volatile变量(命名为state)来维护同步状态, ...
- JUC并发编程--AQS
转自: https://www.jianshu.com/p/d8eeb31bee5c 前言 在java.util.concurrent.locks包中有很多Lock的实现类,常用的有Reentrant ...
- 高并发编程-AQS深入解析
要点解说 AbstractQueuedSynchronizer简称AQS,它是java.util.concurrent包下CountDownLatch/FutureTask/ReentrantLock ...
- JAVA并发-同步器AQS
什么是AQS aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLo ...
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
- java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock
原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...
- 【Java并发编程实战】—– AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...
- Java并发编程系列-(4) 显式锁与AQS
4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...
- java并发编程笔记(六)——AQS
java并发编程笔记(六)--AQS 使用了Node实现FIFO(first in first out)队列,可以用于构建锁或者其他同步装置的基础框架 利用了一个int类型表示状态 使用方法是继承 子 ...
- JUC并发编程基石AQS源码之结构篇
前言 AQS(AbstractQueuedSynchronizer)算是JUC包中最重要的一个类了,如果你想了解JUC提供的并发编程工具类的代码逻辑,这个类绝对是你绕不过的.我相信如果你是第一次看AQ ...
随机推荐
- 深入理解JVM(③)ZGC收集器
前言 ZGC是一款在JDK11中新加入的具有实验性质的低延迟垃圾收集器,目前仅支持Linux/x86-64.ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障.染色指针和内 ...
- GIT本地库基本操作-命令行
GIT本地库操作基本原理 GIT作为分布式版本库软件,每个机器上都是一个版本库. git初始化后,有三个区,分别是 工作区,暂存区,本地库: 工作区是我们编辑代码的区别,包括新增,修改,删除代码操作, ...
- Java并发编程-深入Java同步器AQS原理与应用-线程锁必备知识点
并发编程中我们常会看到AQS这个词,很多朋友都不知道是什么东东,博主经过翻阅一些资料终于了解了,直接进入主题. 简单介绍 AQS是AbstractQueuedSynchronizer类的缩写,这个不用 ...
- springmvc-实现增删改查
30. 尚硅谷_佟刚_SpringMVC_RESTRUL_CRUD_显示所有员工信息.avi现在需要使用restful风格实现增删改查,需要将post风格的请求转换成PUT 请求和DELETE 请求 ...
- django 报错处理汇总
运行 manage.py task时 ,makemigrations抛出以下错误, django.db.utils.OperationalError: (1045, "Access deni ...
- 输入url后浏览器干了些什么(详解)
输入url后浏览器干了些什么(详解) DNS(Domain Name System, 域名系统) 解析 DNS解析的过程就是寻找哪台机器上有你真正需要的资源过程.但你在浏览器张红输入一个地址时,例如: ...
- shell把字符串中的字母去掉,只保留数字
1 编辑测试文件 [root@hz-kvm cephdisk3]# cat > 1.txt <<EOF> 120Tib> EOF 2 显示文件[root@hz-kvm c ...
- 【python + NATAPP】实现内网穿透的简易数据传输
1. 服务端 接收两张图像的地址,返回这两张图像的相似度 import os, shutil, requests import cv2 import numpy as np import imgs_s ...
- python+opencv图像增强——拉普拉斯
img = cv2.imread(r'F:\python\work\cv_learn\clipboard.png',1) cv2.imshow('input',img) kernel = np.arr ...
- 如何通过Elasticsearch Scroll快速取出数据,构造pandas dataframe — Python多进程实现
首先,python 多线程不能充分利用多核CPU的计算资源(只能共用一个CPU),所以得用多进程.笔者从3.7亿数据的索引,取200多万的数据,从取数据到构造pandas dataframe总共大概用 ...