java.util.concurrent.*(并发工具包) 学习笔记
文章目录
1.并发工具类的作用
1. 为了并发安全;
2. 方便线程管理,避免大量锁的创建和销毁,提高运行效率;
3. 线程间协作
2. 线程池
线程池的重要性
1. 反复创建线程与销毁大量的线程开销大
2. 过多的线程会占用太多的时间
线程池的好处
1. 合理利用CPU和内存
2. 方便统一管理线程
3. 避免锁的创建和销毁,加快响应速度
线程池的构造函数重要参数
1. corePoolSize : 核心线程池线程数量
2. maxPoolSize : 线程池最大线程数量
3. keepAliveTime : 闲置线程生存时间, 当线程多余corePoolSize, 如果多余的线程空闲时间超过keep AliveTime,则线程被终止。
4. workQueue : 任务等待队列
5. threadFactory : 线程工厂
6. handler : 拒绝任务策略
线程池工作原理
当一个任务提交给线程池时,
1. 线程池首先判断线程池中线程数量是否小于corePoolSize, 如果小于corePoolSize, 则线程池新建新的线程执行任务;如果大于或等于corePoolSize, 则进入步骤2
2. 当前线程数目等于或大于corePoolSize, 则判断工作队列是否已满,如果未满,则将任务存入工作队列;如果已满,则进入步骤3
3. 线程池判断线程池中的线程是否都处于工作状态,如果没有,创建线程执行任务,如果已经满了,则交给拒绝任务策略handler处理此任务。
饱和策略
1. AbortPolicy : 直接抛出异常
2. CallerRunsPolicy : 有调用者执行线程
3. DiscardOldestPolicy : 丢弃队列中最近的一个任务,并执行当前任务
4. DiscardPolicy : 不处理,直接丢弃
Future接口
Future接口和其实现类用来表示异步计算的结果
Future<String> future = executorService.submit(new CallableThreadPoolTest());
Runnable接口 和Callable接口
都可以被ThreadPoolExecutor执行,它们之间的区别在于Runnable 不会返回结果,而Callable可以返回结果
常见的ThreadPoolExecutor
1. FixedThreadPool : 使用(无界队列)LinkedBlockingQueue
2. SingleThreadPool : corePoolSize = 1 && maximumPoolSize = 1 ,使用LinkedBlockingQueue无界队列
3. cachedThreadPool : 使用有界队列SynchronousQueue, keepAliveTime = 60s
4. ScheduledThreadPoolExecutor : 无界队列,可以设置定期执行任务
3. ThreadLocal
ThreadLocal的作用:
1. 让某个需要用到的对象在线程间隔离(每个线程都有自己的对象)
2. 在任何方法种都可以轻松获取对象
4. 锁
为什么sychronized不够用
1. 效率低 : 只有代码块执行完毕或者中断才会释放锁,不能设定超时,不能中断一个正在试图获得锁的过程
2. 不够灵活 : 加锁和释放锁的时机单一,只有代码执行完毕或发生中断才会释放锁
3. 无法知道是否成功获得锁
Lock的常用方法
lock(), tryLock(), tryLock(long time, TimeUnit unit), lockInterruptibly(), unlock()
队列同步器(AQS)AbstractQueueSynchronizer
使用一个int成员变量state表示同步状态,通过内置的FIFO队列完成获取线程的排队工作,state的更新是通过CAS实现
队列是双向链表
重入锁
重入锁表示能够支持一个线程对资源的重复加锁 ,通过更新state来实现重入锁。
读写锁 ReentrantReadWriteLock
也是通过AQS实现,只不过是讲state按位划分都状态和写状态,高16位是读状态,低16位是写状态。
读写锁降级是指由写锁降级成为读锁
5. 原子类
通过CAS实现原子操作
1. AtomicBoolean
2. AtomicInteger
3. AtomicLong
4. AtomicIntegerArray
5. AtomicReference : 原子引用类,只是引用不能更改,但其指向的类的内容是可以更改的
6. AtomicIntegerFeildUpdater : 原子更新字段类, 如果需要原子的更新某个类里面的某个
6. CAS原理(Compare And Swap)
CAS存在的问题
1. ABA问题,如果一个值由A变成B,然后再由B变成A,那么CAS检查的时候会发现值没有变,但实际变化了。ABA问题的解决思路是加版本号。
2. 循环时间开销大 : 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3. 只能保证一个变量的原子操作
7. 并发容器
1. ConcurrentHashMap
2. ConcurrentLinkedQueue
3. 阻塞队列(BlockingQueue)
HashMap不能用于并发
HashMap在并发执行put操作时可能由于多线程导致HashMap的Entry链表形成环形链表,引起死循环.因为,一旦形成死循环,Entry的next节点永远不为空,就会产生死循环获取Entry.
HashTable 使用Sychronized 来保证线程安全,其对整个Map进行加锁,所有线程必须竞争同一把锁, 效率低下.
ConcurrentHashMap采用锁分段技术,首先将数据分成一段一段存储,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据能被其他线程访问.
ConcurrentHashMap
ConcurrentLinkedQueue
CAS 保证线程安全
阻塞队列(BlockingQueue)
阻塞队列时一个支持两个附加操作的队列,这两个附加的操作支持阻塞的插入和移除方法.
1. 支持阻塞的插入方法: 当队列满是,队列会阻塞插入元素的线程,知道队列不满
2. 支持阻塞的移除方法: 当队列空时,获取元素的线程会等待队列变为非空
Java里的阻塞队列
1. ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列
2. LinkedBlockingQueue : 一个有链表结构组成的有界阻塞队列
3. PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列
4. DelayQueue : 一个支持优先级排序的无界阻塞队列
5. SychronousQueue : 一个不存储元素的阻塞队列
6. LinkedTransferQueue : 一个由链表结构组成的无界阻塞队列
7. LinkedBlockingDeque : 一个由链表结构组成的双向阻塞队列
8. AQS(AbstratQueuedSynchronizer)
核心数据结构 state + 双向链表 其中state用volatile修饰
底层的操作时CAS + 自旋
AQS提供两种资源共享方式: 独占和共享
主要方法: acquire()
tryAcquire()
addWaiter()
release()
9. Java 并发控制工具类
并发工具类
1. Semaphore
CountDownLatch
说明 : 用于倒数结束之前,使当前线程一直处于等待状态,直到倒数结束,此线程才继续工作
主要方法: 构造方法 CountDownLatch(10); // 指定倒计数的大小
latch.await(); // 是当前线程处于等待,当latch倒计数结束后继续执行
latch.countDown(); // 使倒计数减一。
用法 : 1. 一个线程等待多个其他线程执行完毕再继续
2. 多个线程等待一个线程执行完毕再继续
CountDownLatch实例代码
import java.util.concurrent.*;
/**
* @Author : suyc
* @Date : 2020/6/19 9:37
* @Descriptions : 测试一个完整的跑步比赛过程
*/
public class CountDownLatchTest12 {
public static void main(String[] args) throws InterruptedException {
// 定义运动员数量
int num = 10;
// 定义开始时裁判员,在所有选手准备完毕后发起信号枪
CountDownLatch begin = new CountDownLatch(1);
// 定义终点裁判员,直到所有选手都到达终点宣布比赛结束
CountDownLatch end = new CountDownLatch(num);
ExecutorService executorService = Executors.newFixedThreadPool(num);
for(int i = 0; i < num; i ++){
final int no = i + 1;
Runnable athlete = new Runnable(){
@Override
public void run(){
try {
System.out.println("No " + no + "开始准备");
Thread.sleep((long)(Math.random() *