Semaphore

Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。应用场景:资源访问,服务限流。
Semaphore 实现AbstractQueuedSynchronizer的方法与ReentrantLock一样

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;
}
 
 semaphore.release();释放资源,源码分析
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是什么?

CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch如何工作?
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
API
CountDownLatch.countDown()分线程执行------   线程执行完任务之后处于等待状态
CountDownLatch.await();   主线程执行 -------监控所有线程,所有线程结束之后主线程继续走下去。
CountDownLatch 不可重用

CyclicBarrier

栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
API
cyclicBarrier.await();
应用场景
可以用于多线程计算数据,最后合并计算结果的场景。例如,用一个Excel保存了用户所有银行流水,每个Sheet保存一个账户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水
CyclicBarrier 可以重用。

并发编程AQS----共享锁的更多相关文章

  1. Java并发编程--AQS

    概述 抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是用来构建锁或者其他同步组件的基础框架,它使用一个整型的volatile变量(命名为state)来维护同步状态, ...

  2. JUC并发编程--AQS

    转自: https://www.jianshu.com/p/d8eeb31bee5c 前言 在java.util.concurrent.locks包中有很多Lock的实现类,常用的有Reentrant ...

  3. 高并发编程-AQS深入解析

    要点解说 AbstractQueuedSynchronizer简称AQS,它是java.util.concurrent包下CountDownLatch/FutureTask/ReentrantLock ...

  4. JAVA并发-同步器AQS

    什么是AQS aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLo ...

  5. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  6. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  7. 【Java并发编程实战】—– AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...

  8. Java并发编程系列-(4) 显式锁与AQS

    4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...

  9. java并发编程笔记(六)——AQS

    java并发编程笔记(六)--AQS 使用了Node实现FIFO(first in first out)队列,可以用于构建锁或者其他同步装置的基础框架 利用了一个int类型表示状态 使用方法是继承 子 ...

  10. JUC并发编程基石AQS源码之结构篇

    前言 AQS(AbstractQueuedSynchronizer)算是JUC包中最重要的一个类了,如果你想了解JUC提供的并发编程工具类的代码逻辑,这个类绝对是你绕不过的.我相信如果你是第一次看AQ ...

随机推荐

  1. 手把手教你使用Python生成图灵智能小伙伴,实现工作助手/闲聊功能

    /1 前言/ 在家闲着,做个小项目,基于Python,实现一个语聊小机器人,分享给大家.项目整体比较简单,官方文档介绍的非常详细,可快速上手. /2 目标/ 将图灵机器人放到桌面,实现工作助手/陪聊功 ...

  2. ODBC 常见数据源配置整理

    目录 1. 简介 1.1 ODBC和JDBC 1.2 ODBC配置工具 1.3 ODBC 数据源连接配置 2. MySQL 数据源配置 2.1 配置步骤 2.2 链接参数配置 3. SQLServer ...

  3. Spring IoC BeanDefinition 的加载和注册

    前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 本篇文章主要介绍 Spring IoC 容 ...

  4. webpack的入门实践,看这篇就够了

    webpack的入门实践 我会将所有的读者概括为初学者,即使你可能有基础,学习本节之前我希望你具有一定的JavaScript和node基础 文中的 ... ...代表省略掉部分代码,和上面的代码相同 ...

  5. oracle创建序列,并插入记录

    Oracle序列创建和使用创建序列 语法 CREATE SEQUENCE 序列名 [相关参数] 参数说明 INCREMENT BY  : 序列变化的步进,负值表示递减.(默认1) START WITH ...

  6. Centos7安装部署openstack--nova计算服务

    一.概述 使用OpenStack计算服务来托管和管理云计算系统.OpenStack计算服务是基础设施即服务(IaaS)系统的主要部分,模块主要由Python实现. OpenStack计算组件请求Ope ...

  7. python冷知识(续)

    python 冷知识 1.交互式中修改最大递归深度 大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出. 那到底,默认递归次数限制是多少呢? 可以使用sys这个库来查看 >>&g ...

  8. Milk Pumping G&Milk Routing S 题解

    Milk Pumping G&Milk Routing S 双倍经验时间 洛谷P5837 [USACO19DEC]Milk Pumping G 洛谷P3063 [USACO12DEC]Milk ...

  9. JDK8 日期格式化

    SpringBoot 是为了简化 Spring 应用的创建.运行.调试.部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖 ...

  10. 同一WpfApplication下简单的页面转换

    别人写的很不错的Dome...分享学习下 源文件地址 : http://pan.baidu.com/share/link?shareid=1698564707&uk=3912660076