介绍

AQS是java.util.concurrent.locks下类AbstractQueuedSynchronizer的简称,是用于 通过Java源码来构建多线程的锁和同步器的一系列框架,用于Java多线程之间的同步,它的类及类结构图如下:

原理

在AQS类中维护了一个使用双向链表Node实现的FIFO队列,用于保存等待的线程,同时利用一个int类型的state来表示状态,使用时通过继承AQS类并实现它的acquire和release方法来操作状态,来实现线程的同步。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

不同组件的使用

CountDownLatch

主要用于等待线程等待其他线程执行后再执行,其实现是通过控制计数器是否递减到0来判别,其他的每一个线程执行完毕后,调用countDown()方法让计数器减一,等待线程调用await()方法,直到计数器为1在执行。

demo 主线程等待200个线程执行完毕后再执行:

import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** \* Created with IntelliJ IDEA.* \* @author: guohezuzi* \* Date: 2019-06-08* \* Time: 下午4:14* \* Description: ContDownLatch用法:通过引入CountDownLatch计数器,来等待其他线程执行完毕* \*/
@Slf4j
public class CountDownLatchExample {private static int threadCount = 200;public static void test(int threadNum) throws InterruptedException {Thread.sleep(100);log.info("{}",threadNum);Thread.sleep(100);}public static void main(String[] args) throws InterruptedException {ExecutorService pool= Executors.newCachedThreadPool();final CountDownLatch countDownLatch=new CountDownLatch(200);for (int i = 0; i < threadCount; i++) {final int threadNum=i;pool.execute(()->{try {Thread.sleep(1);test(threadNum);}catch (Exception e){log.error("exception",e);}finally {countDownLatch.countDown();}});}countDownLatch.await();log.info("finish");pool.shutdown();}
}
复制代码

CyclicBarrier

用于等待多个线程都准备好再进行,每一个线程准备好后,计数器加1,加到指定值后全部开始

demo 一个20个线程每等待5个线程进行一次

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.*;/*** \* Created with IntelliJ IDEA.* \* @author: guohezuzi* \* Date: 2019-06-08* \* Time: 下午5:20* \* Description:* 用于等待多个线程都准备好* 每一个线程准备好后 计数器加1 加到指定值后全部开始* \*/
public class CyclicBarrierExample {private static final Logger logger = LoggerFactory.getLogger(CountDownLatchExample.class);private static CyclicBarrier cyclicBarrier=new CyclicBarrier(5);public static void race(int threadNum) throws InterruptedException{Thread.sleep(1000);logger.info("{} is ready",threadNum);try {//等待指定数量的其他线程执行 无参一直等待不抛异常 有参数表示等待指定时间若数量还未等到抛出异常cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);} catch (BrokenBarrierException | TimeoutException e) {logger.error("exception",e);}logger.info("{} is continue");}public static void main(String[] args) throws InterruptedException {ExecutorService executorService= Executors.newCachedThreadPool();for (int i = 0; i < 20; i++) {Thread.sleep(1000);final int threadNum=i;executorService.execute(() -> {try {race(threadNum);} catch (InterruptedException e) {logger.error("exception",e);}});}executorService.shutdown();}}
复制代码

Semaphore

英译信号量,用于控制某个资源同时可被访问的个数,如控制数据库资源可以同时并发数量为20

demo:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.*;/*** \* Created with IntelliJ IDEA.* \* @author: guohezuzi* \* Date: 2019-06-08* \* Time: 下午3:39* \* Description: 信号量学习例子 控制某个资源同时可被访问的个数 如控制数据库资源可以同时并发数量为20* \*/
public class SemaphoreExample {private static final Logger logger = LoggerFactory.getLogger(CountDownLatchExample.class);private static int threadCount = 200;public static void test(int threadNum) throws InterruptedException {Thread.sleep(100);logger.info("{}",threadNum);Thread.sleep(1000);}public static void main(String[] args) throws InterruptedException {ExecutorService pool= Executors.newCachedThreadPool();//定义允许并发的信号量mfinal Semaphore semaphore=new Semaphore(20);for (int i = 0; i < threadCount; i++) {final int threadNum=i;//该线程的最大并发数为m/npool.execute(()->{try {//获取n个信号量 无参为一个semaphore.acquire(4);test(threadNum);//释放n个信号量 无参为一个semaphore.release(4);}catch (Exception e){logger.error("exception",e);}});}pool.shutdown();}
}
复制代码

ReentrantReadWriteLock

读写锁,用于需要同步资源时在前后加锁/解锁,当一个线程获取读锁后其他线程可以继续获取读锁,当一个线程获取写锁后其他线程都需等待,因此,可能造成写锁饥饿,就是写锁一直无法获取。

demo: 一个基于aqs锁实现的部分线程安全的map

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** \* Created with IntelliJ IDEA.* \* @author: guohezuzi* \* Date: 2019-06-08* \* Time: 下午11:58* \* Description: 读写锁 当一个线程获取读锁后其他线程可以继续获取读锁 当一个线程获取写锁后其他线程都需等待* \*/
public class ReentrantReadWriteLockExample {final Map map = new TreeMap<>();private final static ReentrantLock lock = new ReentrantLock();private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();private final Lock readLock = readWriteLock.readLock();private final Lock writeLock = readWriteLock.writeLock();public Data get(String key) {readLock.lock();try {return map.get(key);} finally {readLock.unlock();}}public Set getAllkeys() {readLock.lock();try {return map.keySet();} finally {readLock.unlock();}}public Data put(String key, Data vlaue) {writeLock.lock();try {return map.put(key, vlaue);} finally {writeLock.unlock();}}class Data {}
}
复制代码

StampLock

类似读写锁的功能和使用方法,不过有以下两点不同

  1. 每次获取锁会得到一个long类型的stamp所为返回值,解锁是需要将其回传。

  2. 有乐观读操作,适合于读多写少情况,指当资源被读锁锁定时,会根据资源是否被变更,进行读取操作,而不是不允许读操作。

demo:

import java.util.concurrent.locks.StampedLock;/*** \* Created with IntelliJ IDEA.* \* @author: guohezuzi* \* Date: 2019-06-09* \* Time: 下午1:08* \* Description:* 使用是每次获取锁会得到一个long类型的stamp所为返回值,解锁是需要将其回传* 该类有 写 读 乐观读:指当资源被读锁锁定时,会根据资源是否被变更,进行读取操作*/
public class StampLockExample {private int count = 0;private final StampedLock lock = new StampedLock();class AddHundredNum extends Thread {@Overridepublic void run() {
//            synchronized (addHundredNum.class) {long stamp = lock.writeLock();try {for (int i = 0; i < 1000; i++) {count++;}} finally {lock.unlock(stamp);}
//            }}}public void test() throws InterruptedException {StampLockExample.AddHundredNum[] addHundredNums = new StampLockExample.AddHundredNum[100];for (int i = 0; i < addHundredNums.length; i++) {addHundredNums[i] = new StampLockExample.AddHundredNum();}for (StampLockExample.AddHundredNum addHundredNum : addHundredNums) {addHundredNum.start();}for (StampLockExample.AddHundredNum addHundredNum : addHundredNums) {addHundredNum.join();}}public static void main(String[] args) throws Exception {StampLockExample example = new StampLockExample();example.test();System.out.println(example.count);}
}
复制代码

Condition

配合AQS锁实现的线程中断/等待机制,将等待的线程移入condition维护的队列,并通过condition控制中断/等待。

import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;/*** \* Created with IntelliJ IDEA.* \* @author: guohezuzi* \* Date: 2019-06-09* \* Time: 下午1:26* \* Description:* \*/
@Slf4j
public class ConditionExample {public static void main(String[] args){final ReentrantLock lock=new ReentrantLock();Condition condition=lock.newCondition();new Thread(()->{lock.lock();log.info("wait signal");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}log.info("get signal");lock.unlock();}).start();new Thread(() -> {lock.lock();log.info("get lock");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}condition.signalAll();log.info("send signal ~");lock.unlock();}).start();}
}
复制代码

转载于:https://juejin.im/post/5d08a0be6fb9a07edd2a1438

Java多线程 - AQS详解相关推荐

  1. Java 多线程 —— AQS 详解

    引言 AQS 是AbstractQuenedSynchronizer 的缩写,抽象的队列式同步器,它是除了java自带的synchronized关键字之外的锁机制.是 JUC 下的重要组件. 相关产物 ...

  2. java多线程设计模式详解[推荐]

    java多线程设计模式详解[推荐] java多线程设计模式详解之一 线程的创建和启动 java语言已经内置了多线程支持,所有实现Runnable接口的类都可被启动一个新线程,新线程会执行该实例的run ...

  3. Java多线程进阶详解

    文章目录 1.卖票案例引入数据不安全问题 2.同步代码块 深入理解synchronized关键字 3.同步方法与静态同步方法 同步方法 静态同步方法 内置锁 静态同步方法与同步代码块共同使用 为什么要 ...

  4. 40个Java多线程问题详解复习

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群",加入新技术 来源:8rr.co/vXmW 1.多线程有什么用? 一个可能在很多 ...

  5. java多线程设计模式详解

    Java多线程设计模式 线程的创建和启动 Java语言已经内置了多线程支持,所有实现Runnable接口的类都可被启动一个新线程,新线程会执行该实例的run()方法,当run()方法执行完毕后,线程就 ...

  6. java多线程使用详解与案例,超详细

    文章目录 线程lamda表达式方式启动(简单.常用) java使用多线程的三种方式: 继承Thread 实现Runnable 实现Callable 线程池的使用: 守护线程: 使用lamda表达式简化 ...

  7. Java多线程超详解

    引言 随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发.这就要求对线程的掌握很彻底. 那么话不多说,今天本帅将记录自己线程的学习. 程序,进程,线程的基 ...

  8. Java——多线程使用详解

    多线程: 多线程就是同时执行多个应用程序,需要硬件的支持 同时执行:不是某个时间段同时,cpu切换的比较快,所有用户会感觉是在同时运行 并发与并行: 并行(parallel):指在同一时刻,有多条指令 ...

  9. Java—多线程创建详解

    关注微信公众号:CodingTechWork,一起学习进步. 多线程介绍 线程和进程 进程 定义:进程是一块包含了某些资源的内存区域,操作系统利用进程把它的工作划分为一些功能单元.(应用程序是由一个或 ...

最新文章

  1. 230套java web开发PDF书籍和CHM参考手册资料大全 免费下载
  2. 八皇后非递归算法c语言,要求;编写实现八皇后问题的递归解法或非递归解法,对于任意给定的一? 爱问知识人...
  3. sharing-jdbc实现读写分离及分库分表
  4. 基于光强的角点检测(SUSAN角点检测、FAST角点检测、FAST-ER角点检测)
  5. 沃尔玛正测试货架扫描机器人,并称不会取代人类员工
  6. html如何把上边角做成椭圆,使用css3的border-radius和border制作半圆、三角、椭圆等各种图形...
  7. 【Elasticsearch】Elasticsearch 索引 模板 template
  8. 在MongoDB的MapReduce上踩过的坑
  9. 大数据Hadoop简介
  10. (2)香橙派+apache2与php+天猫精灵=自建平台语音支持--香橙派操作系统安装
  11. 3D视频调校技术解决之道重点在3D眼镜
  12. 解决pycharm下载第三方库速度慢的问题
  13. MFC—界面设计(控件自适应,添加背景图,Static背景颜色设置)
  14. 【渝粤题库】陕西师范大学200601 英语报刊阅读
  15. iconv php gbk utf8,PHP通过iconv将字符串从GBK转换为UTF8字符集
  16. 订阅切换按钮(subscribe toggle button)
  17. Cision推出全新媒体关系管理工具:更精准锁定记者与意见领袖
  18. 在Mac OS X下获得电脑屏幕中任意颜色的RGB值
  19. 谁是古代最风流的首席娱乐官?
  20. DataV(对象类)展示8 ~ 20 °C

热门文章

  1. 机器学习2021 | 机器学习算法如何商业落地?
  2. 制造业人工智能8大应用场景
  3. 麦肯锡季刊 | 人工智能的发展与障碍
  4. 一文读懂生物医学领域的传感器
  5. 2018年中美独角兽研究报告
  6. 包揽全球50%以上份额,中美发力超级计算
  7. 国内首档程序员真人秀?这不比博人传热血?!
  8. 程序员之间的门户之见有多深?
  9. 谷歌知名前 AI 研究员无辜被裁,CEO:调查!
  10. 漫画:毕昇 JDK,重现了 “活字印刷术” 的传奇