并发编程
一、概念
1. 并发编程三要素
- 原子性 : 操作要么全部成功,要么全部失败
- 有序性 : 进程按照代码的先后顺序运行
- 可见性 : 多线程访问同一个变量时,其中一个线程对变量进行修改,其他线程能立刻获取到最新的值
2. 锁分类
-
悲观锁
-
在数据【修改前】,就先锁定,造成线程阻塞
-
保证同一时间只能有一个线程访问特定代码段
-
实际案例
-
synchronized
-
等待策略
- 如果拿不到锁,先自旋,自旋还拿不到锁,再阻塞
-
原理
- 锁是对象
- 内部有一个 state 变量(标志位),用于纪录锁有没有被线程占用
- 0 : 没有线程占用
- 1 : 有某个线程占用
- 如果锁被线程占用,纪录该线程的 Thread ID
- 维护一个 Thread Id List,纪录等待获取这个锁的线程(锁池),在当前线程释放锁后,从 List 中取一个线程继续运行
- 内部有一个 state 变量(标志位),用于纪录锁有没有被线程占用
// 两者等价 public void synchronized method1() { } public void method1() { synchronized(this) { } }
// 两者等价 public static void synchronized method2() { } public static void method2() { synchronized(MyClass.class) { } }
- 锁是对象
-
-
ReentrantLock
-
-
-
乐观锁
-
在数据【提交更新时】,才进行版本检测,线程不阻塞
-
实际案例
-
CAS : 如果 预期原值 与 内存值 相匹配,则 CPU 自动将该位置更新为 新值,返回 true;否则则不做操作,返回 false
Compare And Swap 比较替换
-
AtomicInteger : 内部使用 CAS、自旋、volatile 语意 实现
-
-
3. synchronized 修饰
- 代码块
- 作用范围 : 大括号 { } 括起来的代码
- 作用对象 : 调用这个代码块的对象
- 方法
- 作用范围 : 整个方法
- 作用对象 : 调用这个方法的对象
- 静态方法
- 作用范围 : 整个静态方法
- 作用对象 : 该类的所有对象
- 类
- 作用范围 : 类的 { } 括起来的代码
- 作用对象 : 该类的所有对象
二、线程
-
所有 Java 进程,都有一个 Main 线程
-
所有线程都有优先级,默认 5,最高 10 - 通常 高优先级 在 低优先级 线程之前运行
-
分类
-
守护线程 : 通常作为 垃圾搜集器 或 缓存管理器 存在于应用中,运行辅助任务
-
非守护线程【默认】: 所有 非守护线程 结束运行,则应用结束 - 无论守护线程是否正在运行
当 非守护线程 都不存在时,进程结束,守护线程 也会一起结束
-
-
状态
给定时间内,线程只能处于其中一种状态
-
NEW : Thread 对象已创建,但还没有开始运行
-
RUNNABLE : Thread 对象正在运行
-
BLOCKING : Thread 对象正在被阻塞
重量级阻塞,不能被中断 interrupt() - synchronized
-
WAITING : Thread 对象正在等待另一个线程的动作
-
TIME_WAITING : Thread 对象正在等待另一个线程的动作,但有时间限制
WAITING、TIME_WAITING : 轻量级阻塞,能够被中断 interrupt() - wait()、sleep()、join()、park()
-
TERMINATED : Thread 对象已完成运行
-
-
常用方法
- 信息
- getId() : 获取 Thread 对象的标示符,一个正整数,在整个线程生命中唯一,且无法改变
- getName() / setName() : 获取 / 设置 Thread 对象 String 类型的名称
- getPriority() / setPriority() : 获取 / 设置 Thread 对象的优先级
- isDaemon() / setDaemon() : 获取 / 设置 Thread 对象是否为守护线程
- getState() : 获取 Thread 对象的当前状态
- interrupt() : 唤醒轻量级阻塞,给目标线程打上中断标记,如果该线程正在运行的方法有声明抛出 InterruptedException,则会抛出异常 - wait()、join()、sleep()
- interrupted() : 返回目标线程是否有中断标记,并清除中断标记
- isinterrupted() : 返回目标线程是否有中断标记,但不清除中断标记
- sleep(ms) : 线程暂停 ms 时间,且不释放锁
- join() : 调用另一个线程运行,直到另一个线程运行结束,再接着运行
- setUncaughtExceptionHandler() : 创建未校验异常的控制器
- currentThread() : 返回实际运行该代码的 Thread 对象
- 信息
-
创建方式
-
继承 Thread 类
public class MyThread extends Thread { @Override public void run() { } } public class Main { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
-
实现 Runnable 接口
public class MyRunnable implements Runnable { @Override public void run() { } } public class Main { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); } }
-
实现 Callable 接口 - 可获取线程返回值
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(5000); return "hello world call() invoked!"; } } public class Main { public static void main(String[] args) { MyCallable myCallable = new MyCallable(); // 设置 Callable 对象,泛型表示 Callable 的返回类型 FutureTask<String> futureTask = new FutureTask<String>(myCallable); // 启动处理线程 new Thread(futureTask).start(); // 同步等待线程运行的结果 String result = futureTask.get(); // 5s 后得到结果 System.out.println(result); } } public class Main2 { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)) { @Override protected void afterExecute(Runnable r, Throwable t) { // 如果在call方法运行过程中有错误,则可以在此处进行处理 super.afterExecute(r, t); } }; Future<String> future = executor.submit(new MyCallable()); String s = future.get(); System.out.println(s); executor.shutdown(); } }
-
-
沟通
-
Object 类函数
-
和 synchronized 一起使用
-
wait() : 阻塞当前线程,并释放锁,并将该线程加入 等待池
-
notify() : 随机唤醒一个在等待 synchronized 代码块锁的线程,并将该线程加入 锁池
-
notifyAll() : 唤醒全部在等待 synchronized 代码块锁的线程,并将线程加入 锁池
○ 等待池 : 池中的线程 不能 参与锁竞争
○ 锁池 : 池中的线程 可以 参与锁竞争
-
-
Condition
-
和 Lock 一起使用
-
await() : 阻塞当前线程,并释放锁,并将该线程加入 等待池
-
signal() : 精确唤醒被阻塞的一类线程,并将该线程加入 锁池
-
-
三、并发
1. 概念
-
并发 : 在单核处理器上运行多个任务
-
并行 : 同一时间内,在 不同计算机 或 不同处理器 上同时运行多个任务
-
临界段 : 一段代码,给定时间内,只能被一个任务运行
-
不可变对象 : 初始化后,不能修改其可视状态(属性值) - 线程安全
如果想修改,则必须创建一个新的对象
-
原子操作 : 操作要么都成功,要么都失败,可以透过临界段实现
-
原子变量
- 透过原子操作 设置 或 获取 值的操作
- 实现方式
- 同步机制
- CAS : 无锁,不需要同步机制
2. 同步
- 控制同步 : 任务的开始依赖于另一个任务的结束
- 数据访问同步 : 多个任务同时访问共享变量时,任意时间里,只有一个任务可以访问该变量
- 机制
- 信号量
- 存放可用资源数量的变量,采用两种原子操作管理该变量
- 获取
- 释放
- 互斥(特殊的信号量)
- 状态
- 空闲
- 忙 : 只有将状态设置为忙的任务才能释放它
- 状态
- 存放可用资源数量的变量,采用两种原子操作管理该变量
- 监视器 : 有 一个互斥、一个条件变量、两种操作(等待、通报) - 一旦通报该条件,等待它的任务中只有一个会继续运行
- 信号量
3. 任务通信
- 共享内存 : 任务读取、写入使用相同的内存区域 - 为避免问题,共享内存的访问需在临界段完成
- 消息传递
4. 问题
- 数据竞争 : 多个任务在临界段之外对一个共享变量进行写入操作
- 死锁
- 一个线程等待另一个线程释放共享资源,而该线程又等待前述之线程释放另一共享资源
- 发生条件 Coffman
- 互斥 : 死锁涉及的资源是不可共享的
- 占有并等待 : 线程占有某一互斥资源,同时请求另一互斥资源,且在等待时不会释放资源
- 不可剥夺 : 资源只能被线程持有者释放
- 循环等待 : 线程1 等待 线程2 占有的资源,线程2 等待 线程3 占有的资源 ...,线程n 等待 线程1 占有的资源
- 避免方式
- 忽略【常用】: 假设应用不会出现死锁,如果发生死锁则重启应用
- 检测 : 创建专门分析应用状态的任务,如检测到死锁,则采取措施修复 - 结束线程、强制释放资源
- 预防 : 避免 Coffman 条件一条或多条出现
- 规避 : 线程开始前,得到该线程所有使用的资源
- 活锁
- 应用中的两个线程,总因为对方行为而改变自己的状态,导致双方陷入状态变更循环而无法向下运行
- 资源不足
- 解决 : 确保公平原则,所有等待该资源的线程必须在给定时间内占有该资源
- 优先权反转
- 低优先权线程 持有 高优先权线程 所需的资源,将发生优先权反转,低优先权线程 将在 高优先权线程 之前运行
四、内存模型 JMM
Java Memory Model
1. 内存可见性
- 问题产生原因
- L1、L2 缓存为 CPU 私有,L3 缓存为 CPU 共有,但因为有 CPU 缓存一致性协议 MESI,所以多核 CPU 间不会有内存可见性问题,但因为缓存一致性协议造成巨大性能损耗,故又增加了许多 Buffer
- L1、L2、L3 与 主内存 之间是同步的,但 Store Buffer、Load Buffer 与 L1 之间是异步的 - 每个逻辑 CPU 有自己的缓存,缓存 和 主内存 不完全同步,造成在其他线程看来,该线程的运行顺序是颠倒的
2. 重排序
- 类型
- 编译器 : 没有先后依赖关系的语句,编译器可以重新调整语句的运行顺序
- CPU 指令 : 没有依赖关系的多条指令并行
- CPU 内存 : CPU 指令的运行顺序 和 写入主内存的顺序 不完全一致 - 造成内存可见性问题
3. 内存屏障
- 告诉编译器不要对指令进行重排序
- 种类
- LoadLoad : 禁止 读 和 读 重排序
- StoreStore : 禁止 写 和 写 重排序
- LoadStore : 禁止 读 和 写 重排序
- StoreLoad : 禁止 写 和 读 重排序
- 使用方式
- volatile 关键字
- Unsafe 类
- loadFence : LoadLoad + LoadStore
- storeFence : StoreStore + LoadStore
- fullFence : LoadFence + StoreFence + StoreLoad
4. as-if-serial
运行结果不会改变,代码看起来就像是完全串行的从头运行到尾
- 单线程重排序规则
- 单线程运行结果不能改变 - 操作之间没有数据依赖性,可以任意重排序
- 多线程重排序规则
- 保证每个线程 as-if-serial 语意
5. happen-before
- A happen-before B
- A 的运行结果对 B 可见
- 不代表 A 一定在 B 之前运行
- JMM 承诺
- 单线程中的操作,happen-before 对应线程中任意后续操作 - as-if-serial 语意保证
- volatile 变量写入,happen-before 对应后续该变量的读取 - volatile 变量不能重排序,非 volatile 变量可任意重排序
- synchronized 的解锁,happen-before 对应后续该锁的加锁
- final 变量的写入,happen-before 对应对象的读取,happen-before final 变量的读取
- 传递性
- A happen-before B,B happen-before C,则 A happed-before C
6. volatile
- 功能
- 64 位写入原子性
- 保证内存可见性
- 禁止重排序
五、并发容器
BlockingQueue 为接口,其他类为实现类
1. BlockingQueue
-
带阻塞功能的队列
-
当队列已满,阻塞发送者
-
当队列为空,阻塞消费者
-
方法
-
add : 非阻塞,当队列满时抛出异常
-
remove : 非阻塞,当队列空时抛出异常
-
offer : 非阻塞,当队列满时返回 false
-
poll : 非阻塞,当队列空时返回 null
-
put : 当队列满时阻塞
-
take : 当队列空时阻塞
-
-
ArrayBlockingQueue
-
数组实现的环形队列,需传入数组容量
-
实现方式
-
1 把锁 ReentrantLock
-
2 个条件 Condition
-
notEmpty
-
notFull
-
-
-
-
LinkedBlockingQueue
- 单向列表的阻塞队列
- 默认空间大小 Integer.MAX_VALUE
- 实现方式
- 2 把锁 ReentrantLock
- takeLock
- putLock
- 2 个条件 Condition
- notEmpty
- notFull
- 2 把锁 ReentrantLock
-
PriorityQueue
- 按照元素的优先级大小出队列
- 默认空间大小 11,超过则自动扩容
- 实现方式
- 1 把锁 ReentrantLock
- 1 个条件 Condition - notEmpty
-
DelayQueue
-
按照延迟时间从小到大出队列
延迟时间 : 未来将要运行的时间 - 当前时间
-
当 队列空、堆顶元素延迟时间未到 阻塞
-
实现方式
- 1 把锁 ReentrantLock
- 1 个条件 Condition - available
-
-
SynchronousQueue
- 没有容量
- 当调用 put() 线程阻塞,直到另一个线程调用 take(),两个线程才同时解锁
- 模式
- 公平模式 : 先到先配对
- 非公平模式 : 后到先配对
- 实现方式
- 单向链表
- put 节点、take 节点 相遇则出队列
2. BlockingDeque
Deque : Double End Queue 双端队列
-
阻塞的双端队列接口
-
继承 BlockingQueue、Deque
-
LinkedBlockingDeque
- 实现方式
- 双向链表
- 1 把锁 ReentrantLock
- 2 个条件 Condition
- notEmpty
- notFull
- 实现方式
3. CopyOnWrite
-
在写的时候,不是直接写数据,而是把数据拷贝一份进行修改,再透过乐观锁或悲观锁写回 - 【目的】读不加锁
-
CopyOnWriteArrayList
-
CopyOnWriteArraySet
- 使用 Array 实现 Set,保证元素不重复
- 内部封装 CopyOnWriteArrayList
4. ConcurrentLinkedQueue/Deque
- 基于 双向链表,对 head/tail 进行 CAS 操作,实现 入队 和 出队
5. ConcurrentHashMap
- key 无序
- 头节点是 Node 类型,则后面的为 链表
- 头节点是 TreeNode 类型,则后面的为 红黑树
- 对每个 头节点 加锁
- 初始数组长度 16
- 当数组长度小于 64 时,不会将链表转换成红黑树,而是直接扩容
- 链表元素超过 8 时,将链表转换成红黑树
6. ConcurrentSkipListMap
- key 可排序,基于 SkipList(跳查表) 实现
7. ConcurrentSkipListSet
- key 可排序,基于 SkipList(跳查表) 实现
六、同步工具
1. Semaphore 信号量
- 资源数量的并发访问控制
- 当初始资源数为 1 时,退化成排他锁
- 有 公平、非公平 之分
2. CountDownLatch
- 等待多个 Worker 线程运行完才能退出
- 没有 公平、非公平 之分
3. CyclicBarrier
- 协调多个线程同步运行操作
- 可以被重用
- 可以使用响应中断 breakBarrier(),唤醒所有阻塞线程,count 重置为初始值
- 可以设置回调方法,在线程批量完成时运行一次
4. Exchanger
- 用于线程间交换数据
5. Phaser
-
当未达到线程数减到 0 时,唤醒主线程
-
用于替代 CyclicBarrier 和 CountDownLatch
-
等待所有人都到达这个同步点
-
可以在运行期间动态调整要同步的线程个数
-
多个 Phaser 可以组成树状结构
- Phaser 知道自己的父节点,不知道自己的子节点,所以对父节点的操作,是透过子节点来实现的
- 当子 Phaser 中注册的参与者数量等于 0 时,自动向父节点解除注册
七、Atomic 类
1. AtomicInteger、AtomicLong
-
对整数进行加减操作,如需保证线程安全,则 需要加锁(synchronized) 或 使用 Atomic 类
-
内部使用 CAS、自旋、volatile 语意 实现
○【目的】CAS、自旋 : 降低锁范围
○【目的】volatile : 运行结果在多线程间可见
2. AtomicBoolean、AtomicReference
- 实现 Compare 和 Set 操作合在一起的原子性
3. AtomicStampedReference、AtomicMarkableReference
-
解决 ABA 问题 - 不仅比较值,还要比较版本号
从 A 改到 B,再从 B 改到 A
-
AtomicMarkableReference 与 AtomicStampedReference 类似,只是 AtomicMarkableReference 中的版本号是 boolean 类型,而不是整形
4. AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 解决已经存在的类,不能更改其源代码,对其实现成员变量的原子操作
- 限制 : 成员变量必须是 volatile 的基础类型,不能是包装类型
5. AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 实现数组中一个元素的原子操作
6. Striped64、LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator
- 将变量拆分为多个变量,性能相对 Atomic 再提升
- Accumulator 与 Adder 原理相似,只是功能更强大
八、Condition、Lock
1. Condition
-
本身是一个接口,需和 Lock 一起使用
synchronized 需和 wait()、notify() 一起使用
-
使用 Condition 本身还是只有一把锁(同时只有一个线程能运行),但可以精确通知被阻塞的线程
-
使用 await() 方法,阻塞线程
-
使用 signal() 方法,通知被阻塞的线程运行
-
实现原理
- 使用 双向链表 组成队列,维护被阻塞的线程
-
使用方式
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { //... final Object[] items; int takeIndex; int putIndex; int count; // 一把锁+两个条件 final ReentrantLock lock; private final Condition notEmpty; private final Condition notFull; public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; // 构造器中创建一把锁加两个条件 lock = new ReentrantLock(fair); // 构造器中创建一把锁加两个条件 notEmpty = lock.newCondition(); // 构造器中创建一把锁加两个条件 notFull = lock.newCondition(); } public void put(E e) throws InterruptedException { Objects.requireNonNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) // 非满条件阻塞,队列容量已满 notFull.await(); enqueue(e); } finally { lock.unlock(); } } private void enqueue(E e) { // assert lock.isHeldByCurrentThread(); // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = e; if (++putIndex == items.length) putIndex = 0; count++; // put数据结束,通知消费者非空条件 notEmpty.signal(); } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) // 阻塞于非空条件,队列元素个数为0,无法消费 notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } private E dequeue() { // assert lock.isHeldByCurrentThread(); // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E e = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); // 消费成功,通知非满条件,队列中有空间,可以生产元素了。 notFull.signal(); return e; } // ... }
2. 互斥锁 ReentrantLock
-
防止两线程同时对一公共资源进行读写的机制
-
读读互斥、读写互斥、写写互斥
-
支持 Condition
-
概念
- 可重入
- 当一个线程调用 object.lock() 获取到锁,再次调用 object.lock(),仍可获取到该锁
- 通常锁都要设计成可重入,否则将发生死锁
- synchronized
- 公平
- 新线程进来,看到很多线程在排队,自己排到队伍末尾
- 非公平
- 新线程来了之后,直接去抢锁
- 【目的】提高效率,减少线程切换
- 可重入
-
实现类
- NonfairSync【默认】- 非公平锁
- FairSync - 公平锁
-
实现原理
-
AbstractOwnableSynchronizer
- 纪录当前是哪个线程持有锁
-
AbstractQueuedSynchronizer(AQS)
- 一个 state 变量,纪录锁状态,并使用 CAS 保证线程安全,因支持可重入锁,故 state 值可大于 1
-
一个线程安全的无锁 队列 维护所有阻塞线程 - 使用 双向链表 + CAS 实现
-
底层支持一个线程进行 阻塞 或 唤醒 操作
-
Unsafe 类提供 park() 阻塞、unpark() 唤醒
unpark(thread) 实现对一个线程的精准唤醒,notify() 只是唤醒某一个线程
-
-
3. 读写锁 ReadWriteLock
-
读读不互斥,读写互斥,写写互斥
-
采用 悲观读 策略,当第一个线程拿到读锁后,第二个、第三个线程仍可以拿到读锁,可能导致写线程一直拿不到锁,造成写线程 饿死
-
实现类
- ReadLock - 不支持 Condition
- WriteLock - 支持 Condition
-
实现原理
- 使用一把锁,将线程分为 读线程 和 写线程
- 读线程 和 写线程 之间不互斥(可同时拿到这把锁),读线程之间不互斥,写线程之间互斥
- 当 state != 0 时,要么有线程持有读锁,要么有线程持有写锁,两者不能同时成立,因为读和写互斥
-
使用方式
ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); Lock readLock = readWriteLock.readLock(); readLock.lock(); // 进行读取操作 readLock.unlock(); Lock writeLock = readWriteLock.writeLock(); writeLock.lock(); // 进行写操作 writeLock.unlock();
4. StampedLock
-
读读不互斥、读写不互斥、写写互斥
-
采用 乐观读 策略,读不加读锁,读出来时发现数据被修改,再升级为 悲观读,降低 读 的地位,避免写线程被饿死
-
实现原理
- 在读之前给数据状态做一个快照,拷贝到内存中
-
使用方式
- 在读之前再比对一次版本号,如果版本号改变,则说明在读期间有其他线程修改该数据,则将读出来的数据废弃,重新获取悲观读锁,再次读取
class Point { private double x, y; private final StampedLock sl = new StampedLock(); // 多个线程调用该方法,修改x和y的值 void move(double deltaX, double deltaY) { long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } // 多个线程调用该方法,求距离 double distenceFromOrigin() { // 使用“乐观读” 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); }
九、ThreadPool、Future
1. 线程池
-
原理
- 调用方不断向线程池中的任务队列提交任务,线程池中有一组线程,不断地从任务队列中取任务来运行 - 生产者 - 消费者 模型
- 使用阻塞队列 管理任务 与 唤醒线程
-
-
继承关系
-
-
实现类
- ThreadPoolExecutor
- 参数
- corePoolSize : 始终维护的线程个数
- maxPoolSize : 在 corePoolSize 已满、队列也满的情况下,扩充线程至此值
- keepAliveTime : maxPoolSize 中的线程,空闲多长时间将进行销毁,总线程数收缩回 corePoolSize
- timeUnit : keepAliveTime 的时间单位
- blockingQueue : 使用的任务队列类型
- threadFactory : 线程创建工厂 - 【默认】Executors.defaultThreadFactory()
- rejectedExecutionHandler
- 当线程数到达 maxPoolSize,且 blockingQueue 已满的拒绝策略
- 策略
- AbortPolicy【默认】: 抛出异常
- CallerRunsPolicy : 调用者直接在自己的线程里运行
- DiscardPolicy : 丢弃任务
- DiscardOldestPolicy : 丢弃任务队列中最早的任务
- 参数
- ScheduledThreadPoolExecutor
- 可以延迟运行任务 - 【原理】依靠 DelayQueue
- 可以周期性地运行任务
- scheduleAtFixedRate() : 按照固定频率运行任务,与任务运行时间无关
- scheduleWithFixedDelay() : 按照固定频率运行任务,与任务运行时间有关
- 【原理】周期性地运行完任务后,再将任务扔回任务队列中
- ThreadPoolExecutor
-
任务提交流程
- 判断当前线程数是否小于 corePoolSize,如小於则创建新线程
- 判断队列是否已满,如未满则放入队列
- 判断当前线程数是否小于 maxPoolSize,如小於则创建新线程,如大於则根据拒绝策略,拒绝任务 - 如果任务队列是无界的,则永远没有机会走到此步骤
-
正确关闭步骤
-
调用 Executor 的 shutdown() 或 shutdownNow() 方法
○ shutdown() : 不会清空任务队列、只会中断空闲线程
○ shutdownNow() : 清空任务队列、中断所有线程
-
循环调用 Executor 的 awaitTermination() 方法 - 判断线程池是否到达了最终状态
// executor.shutdownNow(); executor.shutdown(); try { boolean flag = true; do { flag = ! executor.awaitTermination(500, TimeUnit.MILLISECONDS); } while (flag); } catch (InterruptedException e) { // ... }
-
-
使用方式
-
public class ThreadPoolExecutorDemo { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 3, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), // new ThreadPoolExecutor.AbortPolicy() // new ThreadPoolExecutor.CallerRunsPolicy() // new ThreadPoolExecutor.DiscardOldestPolicy() new ThreadPoolExecutor.DiscardPolicy() ); for (int i = 0; i < 20; i++) { int finalI = i; executor.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getId() + "[" + finalI + "] -- 开始"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + "["+ finalI + "] -- 结束"); } }); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } executor.shutdown(); boolean flag = true; try { do { flag = !executor.awaitTermination(1, TimeUnit.SECONDS); System.out.println(flag); } while (flag); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程池关闭成功。。。"); System.out.println(Thread.currentThread().getId()); } }
-
-
Executors 工具类
阿里巴巴开发手册载明禁止使用
- 单线程线程池 : newSingleThreadExecutor()
- 固定数目线程池 : newFixedThreadPool()
- 每接收一个请求,创建一个线程 : newCachedThreadPool()
- 单线程,具有周期调度功能的线程池 : newSingleThreadScheduledExecutor()
- 多线程,具有周期调度功能的线程池 : newSheduledThreadPool()
2. CompletableFuture
-
用于提交多线程任务
-
使用方式
- complete() : 用于多线程间沟通,让计算结果可以用 get() 获取
- get() : 运行被提交的任务,直到有返回结果
public class CompletableFutureDemo { public static void main(String[] args) throws ExecutionException,InterruptedException { CompletableFuture<String> future = new CompletableFuture<>(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } future.complete("hello world"); }).start(); System.out.println("获取结果中。。。"); String result = future.get(); System.out.println("获取的结果:" + result); } }
- runAsync(Runnable) : 提交没有返回值的任务
public class CompletableFutureDemo2 { public static void main(String[] args) throws ExecutionException,InterruptedException { CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> { try { Thread.sleep(2000); System.out.println("任务执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } }); // 阻塞,等待任务执行完成 voidCompletableFuture.get(); System.out.println("进程运行结束"); } }
- supplyAsync(Supplier) : 提交有返回值的任务
public class CompletableFutureDemo3 { public static void main(String[] args) throws ExecutionException,InterruptedException { CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } return "这是结果"; } }); String result = future.get(); System.out.println("任务执行结果:" + result); } }
- thenRun(Runnable) : 接着运行下一个任务,两个任务之间无任何关联
public class CompletableFutureDemo4 { public static void main(String[] args) throws ExecutionException,InterruptedException { CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "这是结果"; }).thenRun(() -> { try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务执行结束之后执行的语句"); }); // 阻塞等待任务执行完成 null voidCompletableFuture.get(); System.out.println("任务执行结束"); } }
- thenAccept(Consumer) : 任务可以获取到前一个任务的返回值,但是本身没有返回值
public class CompletableFutureDemo5 { public static void main(String[] args) throws ExecutionException,InterruptedException { CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("返回中间结果"); return "这是中间结果"; }).thenAccept((param) -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务执行后获得前面的中间结果:" + param); }); // 阻塞等待任务执行完成 future.get(); System.out.println("任务执行完成"); } }
- thenApply(Function) : 任务可以获取到前一个任务的返回值,并且本身有返回值
public class CompletableFutureDemo6 { public static void main(String[] args) throws ExecutionException,InterruptedException { CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("返回中间结果"); return "abcdefg"; }).thenApply(new Function<String, Integer>() { @Override public Integer apply(String middle) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("获取中间结果,再次计算返回"); return middle.length(); } }); Integer integer = future.get(); System.out.println("最终的结果为:" + integer); } }
- thenCompose(Function) : 嵌套 CompletableFuture
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { return "hello world"; } }).thenCompose(new Function<String, CompletionStage<Integer>>() { @Override public CompletionStage<Integer> apply(String s) { return CompletableFuture.supplyAsync(new Supplier<Integer>() { @Override public Integer get() { return s.length(); } }); } }); Integer integer = future.get(); System.out.println(integer);
- thenCombine() : 在 2 个 CompletableFuture 完成之后,把 2 个 CompletableFuture 的返回值传进去,再额外做一些事情
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { return "hello"; } }).thenCombine(CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { return "lagou"; } }), new BiFunction<String, String, Integer>() { @Override public Integer apply(String s, String s2) { return s.length() + s2.length(); } }); Integer result = future.get(); System.out.println(result);
- allOf() : 所有的 CompletableFuture 结束,返回 Void 类型
- anyOf() : 任意一个 CompletableFuture 结束,返回 Object 类型
public class CompletableFutureDemo11 { private static final Random RANDOM = new Random(); private static volatile int result = 0; public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture[] futures = new CompletableFuture[10]; for (int i = 0; i < 10; i++) { int finalI = i; CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() { @Override public void run() { try { Thread.sleep(1000 + RANDOM.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } result++; } }); futures[i] = future; } System.out.println(result); // for (int i = 0; i < 10; i++) { // futures[i].get(); // System.out.println(result); // } // Integer allResult = CompletableFuture.allOf(futures).thenApply(new Function<Void, Integer>() { // @Override // public Integer apply(Void unused) { // return result; // } // }).get(); // // System.out.println(allResult); Integer anyResult = CompletableFuture.anyOf(futures).thenApply(new Function<Object, Integer>() { @Override public Integer apply(Object o) { return result; } }).get(); System.out.println(anyResult); } }
十、ForkJoin
-
基于多线程实现分治算法的并行计算框架 - 可以视为单机版的 Map/Reduce - 将一个大任务拆分成多个小任务,再将小任务结果合并
-
相比 ThreadPoolExecutor,可以更好地实现计算的负载均衡,提高资源利用率
-
类
-
ForkJoinTask
- RecursiveAction : 无返回值
- RecursiveTask : 有返回值
- 方法
- compute() : 制定具体业务逻辑
-
ForkJoinPool
-
-
具备一个全局任务队列,每个 Worker 线程还各具备一个局部任务队列
-
当 Worker 线程运行完自己队列的任务后,会窃取其他线程队列中的任务来运行
- 以防有的线程很闲,有的线程很忙
- Worker 线程自己,在队列头部,透过对 top 指针运行加、减操作,实现任务的入队与出队
- 其他 Worker 线程,在队列尾部,对 base 指针进行累加,实现任务出队操作
- 由于是多线程,需要透过 CAS 操作
- 任务队列是环形的,当 top - base = queue.length - 1 时,表示队列满,需要扩容
-
使用 long 型变量保存状态
- AC : 最高 16 比特位,Active 线程数 - parallelism
- TC : 次高 16 比特位,Total 线程数 - parallelism
- ST : 1 比特位,如果是 1,表示 ForkJoinPool 正在关闭
- EC : 15 比特位,阻塞栈的栈顶线程的 wait count
- ID : 16 比特位,阻塞栈的栈顶线程的 ID
-
阻塞栈 Treiber Stack
-
- 用一个无锁链表,实现多个线程的阻塞与唤醒
- 当一个 Worker 线程窃取不到任何任务时,就会进入阻塞栈
- 当有新任务时,唤醒 Worker 线程,并出阻塞栈
-
-
线程状态
- 空闲 : 放在 阻塞栈 里面
- 活跃 : 正在运行某个 ForkJoinTask,但未阻塞
- 阻塞 : 正在运行某个 ForkJoinTask,但因为正在等待子任务运行完成,所以被阻塞
-
方法
- fork()
- 将子任务放入任务队列,并运行
- 如果是 Worker 线程调用,则将任务放入当前线程的局部队列
- 如果是其他线程调用,则将任务放入共享队列中
- join()
- 等待子任务运行完成,并返回子任务运行结果
- 实现方式 : 每个 ForkJoinTask 有多个线程等待它完成,所以每个 ForkJoinTask 就是一个同步对象,当线程调用 join() 时,将阻塞在该 ForkJoinTask 上,当运行完成后,将通知所有等待的线程
- get() : 获取任务结果
- shutdown() : 拒绝新提交的任务,结束 ForkJoinPool
- shutdownNow() : 取消现有 全局队列 与 局部队列 中的任务,并唤醒所有空闲线程,让其自动退出
- fork()
-
-
-
适用场景
- 快速排序
- 求 1 ~ n 总和
十一、设计模式
1. Single Threaded Execution
- 以一个线程运行
- 使用 synchronized 关键字实现,保护 unsafeMethod,使其同时只能由一个线程访问
- 使用场景
- 多线程访问共享资源
- 需确保资源(集合)安全性
2. Immutable
- 确保实例状态不会发生改变
- 使用 final 关键字实现
- 使用场景
- 创建实例后,状态不再发生改变
- 实例共享,且被频繁访问
- 实际案例
- java.lang.String
- java.math.BigInteger
- java.math.Decimal
- java.util.regex.Pattern
- java.lang.Boolean
- java.lang.Byte
- java.lang.Character
- java.lang.Double
- java.lang.Float
- java.lang.Integer
- java.lang.Long
- java.lang.Short
- java.lang.Void
3. Guarded Suspension
-
当守护条件不成立时,就让线程继续等待
-
使用 while、wait()、notify()/notifyAll() 实现
-
public class RequestQueue { private final Queue<Request> queue = new LinkedList<>(); public synchronized Request getRequest() { // 守护条件 while (queue.peek() == null) { try { // 当守护条件不成立时,就继续等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return queue.remove(); } public synchronized void putRequest(Request request) { queue.offer(request); notifyAll(); } }
-
4. Balking
-
当守护条件不成立时,立即中断处理
-
public class Data { private final String filename; private String content; private boolean changed; public Data(String filename, String content) { this.filename = filename; this.content = content; } public synchronized void change(String newContent) { this.content = newContent; this.changed = true; } public synchronized void save() throws IOException { // 当守护条件成立,则正常运行 if (changed) { doSave(); changed = false; } // 当守护条件不成立,则中断处理 else { System.out.println(Thread.currentThread().getName() + "不需要保存"); } } private void doSave() throws IOException { System.out.println(Thread.currentThread().getName() + " 调用doSave, 内容为:" + content); Writer writer = new FileWriter(filename); writer.write(content); writer.close(); } }
-
使用场景
- 当守护条件不成立,则不需要处理
- 守护条件仅在第一次运行时成立
5. Producer-Consumer
-
生产者 将数据交给 Channel,消费者 从 Channel 中获取数据
-
生产者 与 消费者 都有多个,当 生产者 和 消费者 只有一个时,称为 管道(Pipeline) 模式
-
public class Table { private final String[] buffer; private int tail; private int head; private int count; public Table(int count) { this.buffer = new String[count]; this.head = 0; this.tail = 0; this.count = 0; } public synchronized void put(String steamedBread) throws InterruptedException { System.out.println(Thread.currentThread().getName() + " 蒸出来 " + steamedBread); while (count >= buffer.length) { wait(); } buffer[tail] = steamedBread; tail = (tail + 1) % buffer.length; count++; notifyAll(); } public synchronized String take() throws InterruptedException { while (count <= 0) { wait(); } String steamedBreak = buffer[head]; head = (head + 1) % buffer.length; count--; notifyAll(); System.out.println(Thread.currentThread().getName() + " 取走 " + steamedBreak); return steamedBreak; } }
-
-
线程要协调放在 Channel 的东西
-
线程要互斥应该保护的东西
-
实际案例
- BlockingQueue : 阻塞队列
- ArrayBlockingQueue : 基于数组的 BlockingQueue
- LinkedBlockingQueue : 基于链表的 BlockingQueue
- PriorityBlockingQueue : 具有优先级的 BlockingQueue
- DelayQueue : 特定时间后才可以 take 的 BlockingQueue
- SynchronousQueue : 一直传递的 BlockingQueue
- ConcurrentLinkedQueue : 不限制元素个数的线程安全 BlockingQueue
6. Read-Write Lock
-
多个线程可同时读取,但读取时不可写入
-
当线程正在写入时,其他线程不可 读取 或 写入
-
利用线程读操作不会冲突的特性来提高性能
-
public class ReadWriteLock { private int readingReaders = 0; private int waitingWriters = 0; private int writingWriters = 0; private boolean preferWriter = true; /** * 对于读操作: * 1. 如果有正在写入的writer,则当前线程等待writer写入完成 * 2. 如果没有正在写入的writer,但是有正在等待写入的writer,同时偏向写操作 * 则当前线程等待writer写入完成 * * @throws InterruptedException */ public synchronized void readLock() throws InterruptedException { while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) { // 当前线程等待 wait(); } readingReaders++; } public synchronized void readUnlock() { readingReaders--; preferWriter = true; // 唤醒在当前对象上等待的其他线程 notifyAll(); } public synchronized void writeLock() throws InterruptedException { waitingWriters++; try { // 如果有正在读取的线程,或者有正在写入的线程,则当前线程等待被唤醒 while (readingReaders > 0 || writingWriters > 0) { wait(); } } finally { waitingWriters--; } writingWriters++; } public synchronized void writeUnlock() { writingWriters--; preferWriter = false; // 当前线程写入完成,唤醒其他等待的线程进行读写操作 notifyAll(); } }
-
readLock()、writeLock() 都采用了 Guarded Suspension 模式
-
使用场景
- 读操作负载大
- 多读少写
-
实际案例
- ReadWriteLock
7. Thread-Per-Message
-
为每个请求分配一个线程
-
流程
-
Client 向 Host 提交请求
-
Host 启用线程,线程调用 Helper 处理请求
-
-
public class Host { private final Helper helper = new Helper(); public void request(final int count, final char c) { System.out.println("\t请求:【" + count + "," + c + "】开始。。。"); new Thread() { @Override public void run() { helper.handle(count, c); } }.start(); System.out.println("\t请求:【" + count + "," + c + "】结束!!!"); } }
public class Helper { public void handle(int count, char c) { System.out.println("\t\t处理:【" + count + "," + c + "】开始。。。"); for (int i = 0; i < count; i++) { slowly(); System.out.print(c); } System.out.println(""); System.out.println("\t\t处理:【" + count + "," + c + "】结束!!!"); } private void slowly() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
-
使用场景
- 需要快速响应
- 对操作顺序没有要求
- 不需要返回值
-
实际案例
- Thread 类
- Runnable 接口
- ThreadFactory 接口
- Executor 接口
- Executors 类
- ExecutorService 接口
- ScheduledExecutorService 类
-
8. Worker Thread
-
Worker 线程逐个取回工作并处理
-
也被称为 Thread Pool 模式
-
流程
- Client 提交任务给 Channel
- Worker Thread 不断从 Channel 中获取任务来运行
-
public class Channel { private static final int MAX_REQUEST = 100; private final Request[] requestQueue; private int tail; private int head; private int count; private final WorkerThread[] threadPool; public Channel(int threads) { this.requestQueue = new Request[MAX_REQUEST]; this.head = 0; this.tail = 0; this.count = 0; threadPool = new WorkerThread[threads]; for (int i = 0; i < threadPool.length; i++) { threadPool[i] = new WorkerThread("Worker-" + i, this); } } public void startWorkers() { for (int i = 0; i < threadPool.length; i++) { threadPool[i].start(); } } public synchronized void putRequest(Request request) { while (count >= requestQueue.length) { try { wait(); } catch (InterruptedException e) { } } requestQueue[tail] = request; tail = (tail + 1) % requestQueue.length; count++; notifyAll(); } public synchronized Request takeRequest() { while (count <= 0) { try { wait(); } catch (InterruptedException e) { } } Request request = requestQueue[head]; head = (head + 1) % requestQueue.length; count--; notifyAll(); return request; } }
public class WorkerThread extends Thread { private final Channel channel; public WorkerThread(String name, Channel channel) { super(name); this.channel = channel; } @Override public void run() { while (true) { Request reqeust = channel.takeRequest(); reqeust.execute(); } } }
-
-
使用场景
- 提高吞吐量
- 控制 Worker 线程数量
- 调用与运行分离
-
实际案例
- ThreadPoolExecutor
- Executors
9. Future
-
提交任务,先拿到一张提货卡,过段时间透过提货卡,获得任务处理结果
-
流程
- Client 向 Host 提交任务,返回 FutureData
- Host 产生新线程,新线程在 RealData 运行任务,并将任务结果设置到 FutureData
- Client 访问 FutureData 获取任务结果
-
-
public class Host { public Data request(final int count, final char c) { System.out.println("\trequest(" + count + ", " + c + ") 开始"); // 创建FutureData对象 final FutureData future = new FutureData(); // 启动新线程,创建RealData对象 new Thread() { @Override public void run() { RealData realData = new RealData(count, c); future.setRealData(realData); } }.start(); System.out.println("\trequest(" + count + ", " + c + ") 结束"); // 返回提货单 return future; } }
public class FutureData implements Data { private RealData realData = null; private boolean ready = false; public synchronized void setRealData(RealData realData) { // balking,如果已经准备好,就返回 if (ready) { return; } this.realData = realData; this.ready = true; notifyAll(); } @Override public synchronized String getContent() { // guarded suspension while (!ready) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return realData.getContent(); } }
public class RealData implements Data { private final String content; public RealData(int count, char c) { System.out.println("\t组装RealData(" + count + ", " + c + ") 开始"); char[] buffer = new char[count]; for (int i = 0; i < count; i++) { buffer[i] = c; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("\t\t组装RealData(" + count + ", " + c + ") 结束"); this.content = new String(buffer); } @Override public String getContent() { return content; } }
-
使用场景
-
提高响应,并需要获取任务结果
Thread-Per-Message 只能提高响应,但不能获取任务结果
-
-
实际案例
- Callable
- Future
- FutureTask