J.U.C并发包(1)
J.U.C并发包(1)
AbstractQueuedSynchronizer
AbstractQueuedSynchronizer是JUC并发包中锁的底层支持,AbstractQueuedSynchronizer是抽象同步队列,简称AQS,是实现同步器的基础组件,并发包中锁的实现底层就是使用AQS实现,
- 从类图的关系可以看到AQS是一个FIFO的双向队列,内部通过节点head 和 tail 记录队首和队尾元素,队列元素类型为Node。其中Node中Thread变量用来存放进入AQS队列里面的线程
- Node 节点内部SHARED用来标记该线程是获取共享资源时候被阻塞挂起来后放入AQS队列,
- EXCLUSIVE标记线程是获取独占资源时候被挂起后放入AQS队列;
- waitStatus记录当前线程等待状态,分别为CANCELLED(线程被取消了),SIGNAL(线程需要被唤醒),CONDITION(线程在条件队列里面等待),PROPAGATE(释放共享资源时候需要通知其他节点);
- AQS中维持了一个单一的状态信息state,可以通过getState,setState,compareAndSetState 函数修改其值;对于ReentrantLock 的实现来说,state 可以用来表示当前线程获取锁的可重入次数;
- pre记录当前节点的前驱节点,next记录当前节点后继节点
- 调用acquire(int arg)方法获取独占资源,调用release(int arg)方法释放资源;
具体思路:
- 当多个线程同时调用 lock.lock() 获取锁的时候,同时只有一个线程获取到了该锁,其他线程会被转换为 Node 节点插入到 lock 锁对应的 AQS 阻塞队列里面,并做自旋 CAS 尝试获取锁,前提是head的直接后继;
- 如果获取到锁的线程又调用了对应的条件变量的 await() 方法,则该线程会释放获取到的锁,并被转换为 Node 节点插入到条件变量对应的条件队列里面;
- 这时候因为调用 lock.lock() 方法被阻塞到 AQS 队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的 await()方法则该线程也会被放入条件变量的条件队列;
- 当另外一个线程调用了条件变量的 signal() 或者 signalAll() 方法时候,会把条件队列里面的一个或者全部 Node 节点移动到 AQS 的阻塞队列里面,等待时机获取锁。
CountDownLatch
他是一个同步辅助类,可以实现类似阻塞当前线程的功能,使用了给定的计数器进行初始化,该计数器操作是原子操作,同一时刻只能有一个线程操作该计数器。
如上图中,TA线程由于await()方法被阻塞,除非前面的线程调用countDown()方法,当计数器为0,TA就可以继续往下执行。计数器不可重置。
private final static int threadCount = 200; public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); final CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum);
} catch (Exception e) {
log.error("exception", e);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
log.info("finish");
exec.shutdown();
} private static void test(int threadNum) throws Exception {
Thread.sleep(100);
log.info("{}", threadNum);
Thread.sleep(100);
}
Semaphore
他可以控制某个资源可以被多少个线程同时访问,使用Semaphore管理必须要先获取一个许可,执行完毕后释放一个许可,后面的线程才能继续访问,代码演示:
@Slf4j
public class SemaphoreExample1 { private final static int threadCount = 20; public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(3); for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(); // 获取一个许可
test(threadNum);
semaphore.release(); // 释放一个许可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
} private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
}
CyclicBarrier
他是一个同步辅助类,允许一组线程相互等待,直到到达某个公共的屏障点,commonBarrierPoint,CyclicBarrier也具有一个计数器,当计数器达到设置的值,被await()方法阻塞的值会被唤醒,继续执行后续的操作,计数器可以被重置,适合并发情况下需要合并计算的场景。
演示代码如下:
@Slf4j
public class CyclicBarrierExample1 {
private static Logger log = LoggerFactory.getLogger(CyclicBarrierExample1.class); private static CyclicBarrier barrier = new CyclicBarrier(5); public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
} private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await();
log.info("{} continue", threadNum);
}
}
console:
ReentrantLock
ReentrantLock(可重入锁)和Synchronize锁的区别
- 可重入性:二者都是重入锁
- 锁的实现:ReentrantLock通过jdk实现,synchronize是通过jvm实现,但注意ReentrantLock需要释放锁,而synchronize不需要释放锁,由jvm管理,也就是说synchronize不会产生死锁,而ReentrantLock可能产生死锁。
- 性能的区别:synchronize未做优化前,ReentrantLock优于synchronize,但synchronize在经过偏向锁,轻量级锁优化后性能就差不多了
- 功能区别:代码简洁synchronize优于ReentrantLock,锁的细粒度和灵活度ReentrantLock更好。
ReentrantLock独有功能
- 可指定公平锁还是非公平锁:公平锁(先等待的就先获取锁);
- 提供了condition类,可以分组唤醒需要唤醒的线程;
- 提供锁的打断机制,lock.lockInterruptibly()。
ReentrantLock是一种自选锁,内部循环使用CAS操作实现加锁
ReentrantReadWriteLock
ReentrantReadWriteLock是读写锁,维护了一对锁,一个读锁,一个写锁,通过实现ReadWriteLock接口实现了readLock()方法和writeLock()方法。适用于多线程情况下的读写操作,但是要注意如果读操作过于频繁可能会导致写锁饥饿。
StampLock
StampLock有三种控制锁的方式:写,读和乐观读,StampLock会生成票据。
- 乐观读:乐观的认为写入和读取同时发生的概率很少,因此不悲观的使用读取锁定,程序可以查看读取数据时候遭到写入执行的变更之后,大幅提升程序的性能。
- 乐观锁和悲观锁实例如下:
public class LockExample4 { class Point {
private double x, y;
private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
} //下面看看乐观读锁案例
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
double currentX = x, currentY = y; //将两个字段读入本地局部变量
if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
try {
currentX = x; // 将两个字段读入本地局部变量
currentY = y; // 将两个字段读入本地局部变量
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
} //下面是悲观读锁案例
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
if (ws != 0L) { //这是确认转为写锁是否成功
stamp = ws; //如果成功 替换票据
x = newX; //进行状态改变
y = newY; //进行状态改变
break;
} else { //如果不能成功转换为写锁
sl.unlockRead(stamp); //我们显式释放读锁
stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
}
}
} finally {
sl.unlock(stamp); //释放读锁或写锁
}
}
}
}
J.U.C并发包(1)的更多相关文章
- Java并发包源码学习系列:AbstractQueuedSynchronizer
目录 本篇学习目标 AQS概述 AbstractOwnableSynchronizer 同步队列与Node节点 同步状态state 重要方法分析 独占式获取与释放同步状态 共享式获取与释放同步状态 A ...
- HashMap源码详解与对比
前几天工作忙得焦头烂额时,同事问了一下关于Map的特性,刹那间懵了一下,紧接着就想起来了一些关于Map的一些知识,因为只要涉及到Collection集合类时,就会谈及Map类,因此理解好Map相关的知 ...
- SpringBoot启动流程分析(四):IoC容器的初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- Java并发编程:用AQS写一把可重入锁
Java并发编程:自己动手写一把可重入锁详述了如何用synchronized同步的方式来实现一把可重入锁,今天我们来效仿ReentrantLock类用AQS来改写一下这把锁.要想使用AQS为我们服务, ...
- AbstractQueuedSynchronizer 源码解读(转载)
转载文章,拜读了一下原文感觉很不错,转载一下,侵删 链接地址:http://objcoding.com/2019/05/05/aqs-exclusive-lock/ Java并发之AQS源码分析(一) ...
- HashMap 源码赏析 JDK8
一.简介 HashMap源码看过无数遍了,但是总是忘,好记性不如烂笔头. 本文HashMap源码基于JDK8. 文章将全面介绍HashMap的源码及HashMap存在的诸多问题. 开局一张图,先来看看 ...
- ConcurrentHashMap源码解析 JDK8
一.简介 上篇文章详细介绍了HashMap的源码及原理,本文趁热打铁继续分析ConcurrentHashMap的原理. 首先在看本文之前,希望对HashMap有一个详细的了解.不然看直接看Concur ...
- Java:并发笔记-06
Java:并发笔记-06 说明:这是看了 bilibili 上 黑马程序员 的课程 java并发编程 后做的笔记 5. 共享模型之无锁 本章内容 CAS 与 volatile 原子整数 原子引用 原子 ...
- j.u.c: Java并发包的5大块
//TODO Executors: ExecutorService executor = Executors.newFixedThreadPool(10);... newForkJoinPool(). ...
随机推荐
- mysql重启遇到的问题
不知道是不是每次更新 MySQL 软件之后都需要执行数据库升级指令?在我进行过的几次软件升级之后,总会在 MySQL 的日志中见到 “[ERROR] Missing system table mysq ...
- 2019.6.16完成classstack任务
最终信息 4 ShineEternal 任务完成,账号已注销 120 149 80.537%
- 个人永久性免费-Excel催化剂功能第81波-指定单元格区域内容及公式填充
在日常数据处理过程中,需要对缺失数据进行填充时,按一定逻辑规则进行处理,实现快速填充,规范数据源.此篇给大家带来多种填充数据的场景. 业务使用场景 对各种系统中导出的数据,很多时候存在数据缺失的情况, ...
- C语言入门6-选择结构--f语句-switch
一. 什么是选择结构? 选择结构,也称为分支结构!! 选择结构就是根据 给定的判定条件,判断结果, 并根据 判断的结果 来控制程序的流程 (流程图中, 菱形框 是有来判断的 , ...
- python课堂整理16---内置函数
1. abs :求绝对值 print(abs(-1)) 2. all()传入一个可迭代对象,对该对象进行bool值运算,若都为True 就返回True,有一个为假,就返回False print(all ...
- AMD CPU环境下使用android studio,eclipse的Genymotion插件
1.下载安装VirtualBox Genymotion的运行需要此环境(链接) 2.下载安装android模拟器Genymotion 由于官网的下载速度过慢,建议直接百度下载Genymotion(链接 ...
- markdown常用方法
Markdown格式的普及流行要归功于Github和StackOverflow的流行,随着它们越来越流行,它们支持的Markdown格式也越来越流行. 1.优点 1.Markdown通过内容和样式相分 ...
- spring 的权限控制:security
下面我们将实现关于Spring Security3的一系列教程. 最终的目标是整合Spring Security + Spring3MVC 完成类似于SpringSide3中mini-web的功能. ...
- drf之序列化
在django视图中使用serializer 只是使用serializer类编写API视图,没有用到REST框架 app01下的models.py from django.db import mode ...
- MyBatis 简介与入门
简介 什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.My ...