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(). ...
随机推荐
- 如何在vue中监听scroll,从而实现滑动加载更多
首先需要明确3个定义: 文档高度:整个页面的高度 可视窗口高度:你看到的浏览器可视屏幕高度 滚动条滚动高度: 滚动条下滑过的高度 当 文档高度 = 可视窗口高度 + 滚动条高度 时,滚动条正好到底. ...
- Java编程思想:嵌套类
public class Test { public static void main(String[] args) { // Parcell11.test(); // ClassInterface. ...
- 各类最短路算法基本模板-C++
原文转自:https://blog.csdn.net/changjiale110/article/details/77394650 感谢. #define Max 0x3f3f3f3f #define ...
- c++小游戏——贪吃蛇
#include #include #include #include #include <conio.h> #include #include <windows.h> usi ...
- iconfontのsymbol的使用
iconfontのsymbol的使用 iconfont三种方式的优缺点 unicode 优点: 1.兼容性最好,支持ie6+ 2.支持按字体的方式去动态调整图标大小,颜色等等 缺点: 1.不支持多色图 ...
- Baozi Leetcode Solution 205: Isomorphic Strings
Problem Statement Given two strings s and t, determine if they are isomorphic. Two strings are isomo ...
- 个人永久性免费-Excel催化剂功能第63波-当前选择区域的上下左右平移功能及跳转窗口左上角
日常的Excel使用过程中,大部分的时间是在做选择单元格的操作,一般来说都是对指定单元格区域的内容进行一些数据处理.转换生成新数据的过程,那选择指定单元格的步骤,若能够尽最大程度地效率上得到加速,产出 ...
- 使用Mxnet基于skip-gram模型实现word2vect
1. 需求 使用skip-gram模式实现word2vect,然后在jaychou_lyrics.txt数据集上应用 jaychou_lyrics.txt数据集收录了周杰伦从第一张专辑到第十张专辑中的 ...
- 模板配置教程:Phpcms v9怎么更换模板
先分享下大概的步骤: 1.上传模版文件到服务器: 2.在站点管理 里边[模板风格配置]选择新模板: 3.设置不同模型对应模板: 4.修改现有的栏目,匹配新模板: 5.更新栏目缓存.系统缓存,更新HTM ...
- 深入分析Elastic Search的写入过程
摘要 之前写过一篇ElasticSearch初识之吐槽,不知觉竟然过去了两年了.哎,时光催人老啊.最近又用到了ES,想找找过去的总结文档,居然只有一篇,搞了半年的ES,遇到那么多的问题,产出只有这么点 ...