Java异步,并发与多线程
1.线程
1.1 线程创建
1.1.1 start和run
- start方法启动线程,实现多线程运行,无需等待run执行完毕可以继续执行下面代码
- 调用Thread类的start方法启动线程,处于就绪状态
- run线程体,包含要执行的线程内容,线程进入运行状态,开始运行run函数的代码
1.2 线程状态
- 新建状态:new线程后
- 就绪:调用start
- 运行:获得cpu,执行run
- 阻塞:1.等待阻塞:wait方法,进入等待队列。 2.同步阻塞:获取对象同步锁时,其被占用,则放入锁池。 3.其他阻塞:现场sleep或join或发出IO请求,设置为阻塞,请求完转入可运行状态
- 死亡:1.正常死亡:run/call完成 2.异常结束:线程抛出未捕获的异常。 3.调用stop:容易死锁。
Object:wait notity notityAll
Thread: join sleep yield interrupt
1.2.1 wait:
线程会阻塞直到:1.其他线程notify/notifyAll,2.其他线程调用该线程interrupt方法,该线程抛异常 3.超时返回
当wait时 没有获得监视器锁,会抛异常,共享变量使用synchronized修饰才能获取监视器锁
- 虚假唤醒:一个线程挂起之后没有notify等操作变为运行状态。需要循环测试是否满足唤醒条件,不断调用wait
// 生产者
synchronized (queue) {
while (queue.size() == MAX_SIZE){
try {
// 挂起当前线程,并释放queue的锁
queue.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
queue.add(element);
// 通知消费者线程
queue.notifyAll();
}
// 消费者
synchronized (queue) {
while (queue.size() == 0){
try {
// 挂起当前线程,并释放queue的锁
queue.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
queue.take(element);
// 通知生产者线程
queue.notifyAll();
}
1.2.2 notify:
唤醒该共享变量上wait被挂起的线程,随机唤醒。被唤醒的线程必须获取共享变量的锁之后才能返回
1.2.3 notifyAll:
唤醒全部
1.2.4 join:
等待线程执行终止,调用join的线程会被阻塞,直到被调用线程执行完毕
1.2.5 sleep:
线程让出指定时间的执行权,但是锁不会让出,时间到了正常返回
1.2.6 yield:
让出CPU执行权,告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。
1.2.7 interrupt:
线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理
- 线程A因为调用了wait、join sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException异常而返回。
- isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。
1.3 线程上下文切换
线程使用完时间片,cpu换线程
1.4 死锁
条件:互斥,请求并持有,不可剥夺,环路等待
1.5 守护线程与用户线程
也称服务线程,为用户线程提供公共服务,在Daemon线程中产生的新线程也是Daemon的,线程是JVM级别的,停止Web应用线程依旧活跃
- 优先级:较低
- 设置:在线程对象创建之前 用setDaemon将用户现场设置为守护线程
1.6 ThreadLocal
提供线程本地变量,访问ThreadLocal变量的线程都会有一个本地副本。
public class ThreadLocalTest {
static ThreadLocal<String> localV = new ThreadLocal<String>();
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
localV.set("one");
print("one");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
localV.set("two");
print("two");
}
});
thread.start();
thread2.start();
}
static void print(String s) {
System.out.println(s + " " + localV.get());
}
}
1.7 线程间通信
方法:等待-通知、共享内存、管道流
2.并发编程
2.1 线程安全问题
以自增运算++为例,比如三个线程并发进行自增运算,三个线程同一时间读取amount值,读到的都是100,增加1后结果为101,三个线程都将结果存入到amount的内存,amount的结果是101,而不是103。
多个线程操作相同资源(如变量、数组或者对象)时就可能出现线程安全问题
临界区资源表示一种可以被多个线程使用的公共资源或共享数据,但是每一次只能有一个线程使用它。一旦临界区资源被占用,想使用该资源的其他线程则必须等待。
为了避免竞态条件的问题,我们必须保证临界区代码段操作必须具备排他性。这就意味着当一个线程进入Critical Section执行时,其他线程不能进入临界区代码段执行。
2.2 共享变量内存可见性
Java内存模型,将所有变量放到主内存,线程使用时,把主内存里面复制到自己的工作内存。
2.3 synchronized 关键字
同步锁,可以把任意一个非NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
内部锁,监视器锁。
- 作用范围:1. 作用于方法锁住是对象实例this
2. 作用于静态方法,锁住Class实例,会锁住调用该方法的线程
3. 作用于对象实例,锁住是所有以该对象为锁的代码块 - 核心组件:1.Wait Set 调用wait方法被阻塞的线程被放置在这里
2.Contention List:竞争队列,所有请求锁的线程被放这里
3.Entry List: 竞争队列中那些有资格成为候选资源的线程被移动到这里
4.OnDeck:任意时刻 最多只有一个线程在竞争锁资源,该线程被称为onDeck
5.Owner:当前已经获取到资源的线程
6.!Owner:当前释放的线程
2.4 volatile
当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。
synchronized 是独占锁,同时只能有一个线程调用get()方法,其他调用线程会被阻塞,同时会存在线程上下文切换和线程重新调度的开销,这也是使用锁方式不好的地方。
volatile 是非阻塞算法,不会造成线程上下文切换的开销。但并非在所有情况下使用它们都是等价的,volatile虽然提供了可见性保证,但并不保证操作的原子性。
当写入变量不依赖当前值时使用:读改写就不能使用
2.5 原子性操作
- synchronized 来实现线程安全性,即内存可见性和原子性
- 非阻塞CAS算法实现的原子性操作AtomicLong:使用锁导致线程上下文切换和调度开销。
CAS:Compare and Swap,通过硬件来保证比较-更新的原子性,JDK里面的Unsafe类提供方法
3.锁
3.1 乐观锁
拿数据不上锁,更新时比较版本号,加锁操作,更新失败后指定重试次数,重新获取数据
update xxx set xxx where version = #{version}
CAS操作实现,比价当前值和传入值是否一样,一样更新
3.2 悲观锁
在数据被处理前先加锁,处理中处于锁定状态。
select * from table where xxx for update
AQS框架下,先尝试CAS乐观锁获取锁,拿不到转化为悲观锁
3.3 非公平锁
JVM按随机,就近原则分配锁的机制成为不公平锁
ReentrantLock pairLock = new ReentrantLock(false) //默认
3.4 公平锁
分配锁的机制是公平的,通常先对锁提出请求的线程先被分配到锁,会带来性能开销
ReentrantLock pairLock = new ReentrantLock(true)
3.5 独占锁
只有一个线程能得到锁,悲观锁
ReentrantLock
3.6 共享锁
同时多个线程持有,乐观锁
ReadWriteLock:读写锁允许多个线程同时读取数据,但写入时独占锁,适合读多写少的场景。
3.7 可重入锁
线程再次获取已经获取的锁不被阻塞的锁
3.8 自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
必须设定最大等待时间,超过最大时间停止线程并阻塞
4.JUC
JUC(java.util.concurrent)并发包对操作系统的底层CAS原子操作进行了封装,为上层Java程序提供了CAS操作的API
4.1 ThreadLocalRandom
随机数生成器,Random在多线程时,多个线程竞争同一个原子变量的更新操作,CAS操作,导致大量线程自旋重试。
原理:每个线程复制一份变量
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < 5; i++) {
System.out.println(random.nextInt(5));
}
4.2 原子变量操作类
4.2.1 Atomic类
AtomicLong原子性递增递减,内部使用Unsafe类实现
在高并发下,该类存在性能问题,使用LongAdder类替换
public class CacheWithAtomic {
private final AtomicReference<String> cache = new AtomicReference<>();
public void write(String value) {
cache.set(value);
}
public String read() {
return cache.get();
}
}
4.2.2 LongAdder
原理:竞争一个变量的更新分解为多个变量,内部维护多个Cell变量
4.3 并发List
4.3.1 CopyOnWriteArrayList
线程安全的ArrayList,对其修改操作在底层的一个复制的数组上进行,写时复制策略
内部ReentrantLock保证同时只有一个线程进行修改
4.4 锁
4.4.1 LockSupport
工具类,挂起和唤醒线程
与每个使用它的线程关联一个许可证,默认没有许可证
- park
调用park线程拿到许可证,会马上返回,否则阻塞挂起。其他线程调用unpark,并将当前线程作为参数,被阻塞的当前线程会返回。 - unpark
使线程获取许可证
4.4.2 AQS抽象同步队列
AbstartctQueuedSynchronizer,实现锁的底层。
4.4.3 ReentrantLock
ReentantLock 继承接口Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等
避免多线程死锁的方法。
4.4.3 ReentrantReadWriteLock
采用读写分离策略,允许多个线程同时获取,写少读多的情况
4.4.4 StampedLock
提供三种模式的读写控制,写锁writeLock,悲观锁readLock,乐观锁tryOptimisticRead
4.5 并发队列
4.5.1 ConcurrentLinkedQueue
非阻塞队列,单向链表加CAS实现的入队出队
4.5.2 LinkedBlockingQueue
有界链表,独占锁实现的阻塞队列
4.5.3 ArrayBlockingQueue
有界数组,独占锁实现的阻塞队列
4.5.4 PriorityBlockingQueue
带优先级的无界阻塞队列,出队返回优先级最高/低的元素。使用平衡二叉树实现,直接遍历不保证有序
4.5.5 DelayQueue
无界阻塞延迟队列,每个元素都有过期时间,获取元素时,只有过期元素出队列
4.6 线程同步器
4.6.1 CountDownLatch
主线程等待子线程完成后汇总场景,join不够灵活
CountDownLatch可以在子线程运行任何时候让await返回,不一定等到结束。使用线程池时,直接添加到线程池的没有办法使用join
4.6.2 CyclicBarrier回环屏障
让一组线程全部到达一个状态再全部同时执行,
5.异步
5.1 显式使用线程和线程池实现
5.1.1 显式使用线程
- 1.实现Runnable接口
public class SyncExample {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
// 第一种
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
task1();
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Java8写法
// Thread thread = new Thread( () -> {
// try {
// task1();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }, "SyncExample");
task2();
// 同步等待1任务结束
thread.join();
System.out.println(System.currentTimeMillis() - start);
}
public static void task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
}
public static void task2() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task2");
}
}
- 2.实现Thread类重写run方法
public class SyncExample2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Thread thread = new Thread("Example"){
@Override
public void run() {
try {
task1();
} catch (Exception e) {
e.printStackTrace();
}
}
};
task2();
// 同步等待1任务结束
thread.join();
System.out.println(System.currentTimeMillis() - start);
}
public static void task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
}
public static void task2() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task2");
}
}
问题:
- 每当执行异步任务时,直接创建一个Thread,线程创建与销毁有开销,并且没有限制线程个数,应用线程池
- Thread执行异步任务没有返回值,需要用Future
- 每次都创建是命令式编程方式,需要声明式编程方法,即告诉程序我们要异步执行,但如何实现应该对我们透明
5.1.2 线程池实现
- 无返回结果
public class SyncExample3ThreadPool {
/**
* 线程池核心线程个数为当前CPU核数
*/
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
/**
* 最大线程个数为CPU核数2倍
* 拒绝策略为CallerRunsPolicy,当线程池任务饱和,执行拒绝策略不会丢弃新任务,使用调用线程执行
* 使用命名的线程创建工厂来追溯业务
*/
public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
POOL_EXECUTOR.execute( () -> {
try {
task1();
} catch (Exception e) {
e.printStackTrace();
}
});
task2();
System.out.println(System.currentTimeMillis() - start);
// 挂起当前线程
Thread.currentThread().join();
}
}
- 带返回值
使用Callable类型任务提交到线程,返回Future对象,以阻塞的方式获取结果
public class SyncExample3Return {
/**
* 线程池核心线程个数为当前CPU核数
*/
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
/**
* 最大线程个数为CPU核数2倍
* 拒绝策略为CallerRunsPolicy,当线程池任务饱和,执行拒绝策略不会丢弃新任务,使用调用线程执行
* 使用命名的线程创建工厂来追溯业务
*/
public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws Exception {
// 必须阻塞线程才能获取结果
Future<?> taskReturn = POOL_EXECUTOR.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return task1();
}
});
// Lambda写法
// Future<?> taskReturn = POOL_EXECUTOR.submit(() -> task1());
System.out.println(taskReturn.get());
}
public static String task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
return "Task1";
}
}
5.2 JDK中Future实现
JUC中,提供一些方法来检查计算结果是否已经完成,也提供获取计算结果的方法。
5.2.1 Future接口方法
- get: 等待异步计算完成并返回结果
如果当前任务计算未完成会阻塞,直到完成
如果其他线程取消/中断,抛出异常 - 带超时时间的get:
任务没有被计算出来前不会一直阻塞,会等待timeout时间后抛异常 - isDone: 任务是否正常完成
- cancel: 取消任务
已经完成或已经取消,失败
没被执行,不会执行
已经运行,根据mayIntereuptIfRunning决定是否中断,true中断 - isCancelled:是否取消
5.2.2 FutureTask
可被取消的异步计算任务,实现Future接口
任务可以是Callable和Runnable类型
任务完成后不能重启,除非使用runAndReset方法
弊端:
- 不能清楚地表达多个FutureTask之间关系
- get方法还是会阻塞线程
public class FutureExample {
public static String task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
return "Task1";
}
public static void main(String[] args) throws Exception {
// 创建任务
FutureTask<String> futureTask = new FutureTask<String>( () -> {
String res = null;
try {
res = task1();
} catch (Exception e) {
e.printStackTrace();
}
return res;
});
// 显式执行A
Thread thread = new Thread(futureTask, "threadA");
// 线程池执行
// POOL_EXECUTOR.execute(futureTask);
thread.start();;
// 等待A完成
String resA = futureTask.get();
System.out.println(resA);
}
}
5.2.3 FutureTask类详解
5.2.4 CompletableFuture
通过编程方式显式设置计算结果和状态以让任务结束的Future,并且作为CompletionStage(计算阶段),当它的计算完成时触发一个函数
多个线程调用一个CompletableFuture的complete,cancel方式时只有一个会成功
- 显式使用
public class CompletableFutureTest {
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = new CompletableFuture<String>();
POOL_EXECUTOR.execute( () -> {
// 模拟计算
try {
Thread.sleep(3000);
} catch (Exception e){
e.printStackTrace();
}
// 设置计算结算到future
future.complete("Hello World");
});
// 等待结果,complete完成后,由get而被阻塞的线程被激活
System.out.println(future.get());
}
}
- 异步计算和结果转换
- 基于runAsync
public class CompletableFutureNoRes {
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws Exception {
runAsync();
}
public static void runAsync() throws Exception {
// 默认情况下 使用JVM唯一的ForkJoinPool.commonPool()线程池执行
CompletableFuture future = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e){
e.printStackTrace();
}
System.out.println("end");
}
});
// 使用自己线程池
CompletableFuture futureMy = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e){
e.printStackTrace();
}
System.out.println("end");
}
}, POOL_EXECUTOR);
// 无返回值 结果为null
System.out.println(future.get());
}
}
- 有返回值
public class CompletableFutureWithRes {
private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(8, 8, 1, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10));
// A完成后激活B 但是B拿不到A的结果
public static void main(String[] args) throws Exception {
// 1.基于supplyAsync 获取线程结果
// 默认commonPool, 参数使用自己线程池
CompletableFuture futureA = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
// 执行任务
System.out.println("futureA");
return "Hello";
}
}, bizPoolExecutor);
// 2.thenRun A完成后激活B 但是B拿不到A的结果,也无法get获取到结果
CompletableFuture futureB = futureA.thenRun(new Runnable() {
@Override
public void run() {
System.out.println("futureB");
}
});
// 3.thenAccept A完成后激活B B可以拿到A的结果,也无法get获取到结果
CompletableFuture futureC = futureA.thenAccept(new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println("A:" + o);
System.out.println("futureC");
}
});
// 4.thenApply B可以拿到A执行结果,可以获取get执行结果
CompletableFuture futureD = futureA.thenApply(new Function<String, String>() {
@Override
public String apply(String o) {
System.out.println("A:" + o);
return "futureD";
}
});
System.out.println(futureD.get());
// 5.whenComplete 设置回调函数 不阻塞调用线程
futureA.whenComplete(new BiConsumer<String, Throwable>() {
@Override
public void accept(String o, Throwable o2) {
if (o2 == null) {
System.out.println("A:" + o);
} else {
o2.printStackTrace();
}
}
});
// 挂起等待任务执行完毕
Thread.currentThread().join();
}
}
- 多个任务
public class TwoCompletableFuture {
// 多个CompletableFuture进行组合运算
public static void main(String[] args) throws Exception {
String ID = "ID1234";
// 1.基于thenCompose 一个CF执行完毕 执行另一个
CompletableFuture res = taskOne(ID).thenCompose(id -> taskTwo(id));
System.out.println(res.get());
// 2.基于thenCombine 俩个任务都完成后,使用这俩个参数
CompletableFuture res2 = taskOne(ID).thenCombine(taskTwo("234"), (one, two) -> {
return one + " " + two;
});
System.out.println(res2.get());
// 3.基于allOf 多个并发任务都完成
List<CompletableFuture<String>> futureList = new ArrayList<>();
futureList.add(taskOne("1"));
futureList.add(taskOne("2"));
futureList.add(taskOne("3"));
// 转换为一个
CompletableFuture<Void> res3 = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));
System.out.println(res3.get());
// 4.基于anyOf 等任一一个完成
List<CompletableFuture<String>> futureList2 = new ArrayList<>();
futureList.add(taskOne("1"));
futureList.add(taskOne("2"));
futureList.add(taskOne("3"));
// 转换为一个
CompletableFuture<Object> res4 = CompletableFuture.anyOf(futureList.toArray(new CompletableFuture[futureList.size()]));
System.out.println(res4.get());
}
public static CompletableFuture<String> taskOne(String id){
return CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return id;
}
});
}
public static CompletableFuture<String> taskTwo(String id){
return CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return id + "My";
}
});
}
}
- 异常处理
public class ExceptionCompletableFuture {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = new CompletableFuture<String>();
new Thread( () -> {
try {
throw new Exception();
} catch (Exception e) {
// 如果没有这个 get会一直阻塞
future.completeExceptionally(e);
}
}).start();
// System.out.println(future.get());
// 出现异常返回默认值
System.out.println(future.exceptionally( t -> "Exception").get());
}
}
- Stream流中使用
public class StreamExample {
public static String myCall(String ip){
System.out.println(ip);
return ip + "Success";
}
public static void main(String[] args) {
try {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add("192.168.0." + i);
}
List<CompletableFuture<String>> futureList = list.stream().map(ip ->
CompletableFuture.supplyAsync(() -> myCall(ip))).collect(Collectors.toList());
futureList.stream().forEach(r -> System.out.println(r));
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.3 Spring框架中异步
5.3.1 TaskExecutor
public interface TaskExecutor {
void execute(Runnable task)
}
Spring中的实现:
- SimpleAsyncTaskExecutor:该实现不会复用线程,对于每个请求会新创建一个对应的线程来执行。
它支持的并发限制将阻止任何超过限制的调用,通过setConcurrencyLimit方法来限制并发数,默认不限制并发数 - SyncTaskExecutor:不会异步执行提交的任务,而是同步调用线程来执行,这种实现主要用户没有必要多线程进行处理的情况。
- ConcurrencyTaskExecutor:
- SimpleThreadPollTaskExecutor:
- ThreadPoolTaskExecutor:
- TimerTaskExecutor:
SpringBoot使用TaskExecutor实现
- 启动类
@EnableAsync
@SpringBootApplication
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class);
}
/**
* 核心线程数10:线程池创建时初始化的线程数
* 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
* 缓冲队列200:用来缓冲执行任务的队列
* 允许线程的空闲时间60秒:超过了核心线程数之外的线程,在空闲时间到达之后会被销毁
* 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
* 线程池对拒绝任务的处理策略:此处采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已被关闭,则会丢弃该任务
* 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
* 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
*/
@EnableAsync
@Configuration
class TaskPoolConfig{
@Bean("taskExecutor")
public TaskExecutor taskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 核心进程数
taskExecutor.setCorePoolSize(5);
// 最大线程个数
taskExecutor.setMaxPoolSize(10);
// 超过线程个数的线程空闲多久被回收
taskExecutor.setKeepAliveSeconds(60);
// 缓存队列大小
taskExecutor.setQueueCapacity(20);
// 拒绝策略 CallerRunsPolicy:队列满了,且所有线程忙碌时,新任务不再异步执行,而是调用新线程来执行
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor .CallerRunsPolicy());
taskExecutor.setThreadNamePrefix("TaskExecutor-");
// 等任务执行完毕才退出
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(60);
taskExecutor.initialize();
return taskExecutor;
}
}
}
- AsyncExecutorExample
@Service
public class AsyncExecutorExample {
@Resource
private TaskExecutor taskExecutor;
public void task(){
for (int i = 0; i < 6; i++) {
taskExecutor.execute(new MessagePrinter("Message" + i));
}
}
/**
* task任务执行完后不会退出因为不是守护线程,需要显示关闭
* ThreadPoolTaskExecutor
*/
public void shutdown(){
if (taskExecutor instanceof ThreadPoolTaskExecutor){
((ThreadPoolTaskExecutor)taskExecutor).shutdown();
}
}
private class MessagePrinter implements Runnable{
private String message;
public MessagePrinter(String message) {
this.message = message;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + message);
}
}
}
- TestController
@RestController
public class TestController {
@Autowired
private AsyncExecutorExample asyncExecutorExample;
@GetMapping("/test")
@ResponseBody
public void test() {
asyncExecutorExample.task();
asyncExecutorExample.shutdown();
}
}
5.3.2 @Async实现
调用线程在含有调用@Async方法时立即返回,实际执行发生在Spring中的TaskExecutor异步处理线程中
- AsyncAnnotationExample
@Service
public class AsyncAnnotationExample {
/**
* 指定线程池
* 返回值必须为Future及其子类
*/
@Async("bizExecutor")
public void task(){
for (int i = 0; i < 6; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
/**
* 有返回值
* @return
*/
@Async
public CompletableFuture<String> taskWithRes(){
CompletableFuture<String> res = new CompletableFuture<String>();
System.out.println(Thread.currentThread().getName() + ": " + "Task");
res.complete("success");
return res;
}
}
- TestController
@GetMapping("/test")
@ResponseBody
public void test() {
try {
// 无返回值
asyncAnnotationExample.task();
// 有返回值
CompletableFuture<String> res = asyncAnnotationExample.taskWithRes();
res.whenComplete(new BiConsumer<String, Throwable>() {
@Override
public void accept(String s, Throwable throwable) {
if (throwable == null) {
System.out.println(s);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
- MyAsyncUncaughtExceptionHandler
@Component
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
/**
* 当调用get方法时等待结果会抛出异常,对于void类型 无法被捕获且无法传输,可以使用该类处理该类型
*/
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
ex.printStackTrace();
}
}
@Async原理
5.反应式编程中的异步
5.1 反应式编程
Reactive Programming 涉及数据流和变化传播的异步编程范式。
- 及时响应式:系统及时作出响应
- 回弹性:系统面临失败仍然保持及时响应性
- 弹性:系统在不断变化的工作负载下仍然保持及时响应性
- 消息驱动:依靠异步消息传递在组件之间建立边界,确保松耦合,隔离和位置透明性,边界提供将故障委派为消息投递出去的方法。
5.2 Reactive Streams规范
目的提供一个使用非阻塞回压功能对异步流进行处理的标准
5.3 基于RxJava实现异步
RxJava是Reactive Extensions的Java VM实现,是一个库,用于通过使用可观察序列来编写异步和基于事件的程序
- 依赖
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>2.2.10</version>
</dependency>
- 基本使用
public static void main(String[] args) {
List<Person> list = new ArrayList<Person>();
// 过滤与输出
Flowable.fromArray(list.toArray(new Person[0]))
.filter(p -> p.getAge() >= 10)
.map(p -> p.getName())
.subscribe(System.out::println);// 输出
}
RxJava操作运算符不能直接使用Threads或ExectutorServices进行异步处理,需要使用Schedulers来抽象统一API背后的并发调度线程池。
- Schedulers.io:在动态变化的集合上运行类IO或阻塞操作
- Schedulers.computation:在后台运行固定数量的专用线程来计算密集型工作
- Schedulers.single: 顺序和FIFO方式在单个线程上运行
- Schedulers.from(Executor): 将线程池包装到Schedulers中
.observeOn(Scheduler.io()) // 切换到IO线程
5.4 基于Reactor实现
6.Spring WebFlux
7.异步框架和中间件
7.1 Netty
7.2 Dubbo
7.3 Disruptor
7.4 Akka
7.5 RocketMQ
6.线程池
ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交、线程管理、监控等方法。
1.七个参数
-
corePoolSize:核心线程数
线程池维护的最小线程数量,核心线程创建后不会被回收(注意:设置allowCoreThreadTimeout=true后,空闲的核心线程超过存活时间也会被回收)。
大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收。
线程池刚创建时,里面没有一个线程,当调用 execute() 方法添加一个任务时,如果正在运行的线程数量小于corePoolSize,则马上创建新线程并运行这个任务。 -
maximumPoolSize:最大线程数
线程池允许创建的最大线程数量。
当添加一个任务时,核心线程数已满,线程池还没达到最大线程数,并且没有空闲线程,工作队列已满的情况下,创建一个新线程并执行。 -
keepAliveTime:空闲线程存活时间
当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收。
可被回收的线程:设置allowCoreThreadTimeout=true的核心线程和大于核心线程数的线程(非核心线程)。 -
unit:时间单位:keepAliveTime的时间单位:
TimeUnit.NANOSECONDS
TimeUnit.MICROSECONDS
TimeUnit.MILLISECONDS // 毫秒
TimeUnit.SECONDS
TimeUnit.MINUTES
TimeUnit.HOURS
TimeUnit.DAYS -
workQueue:工作队列
存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。
它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。
JDK默认的工作队列有五种:
ArrayBlockingQueue 数组型阻塞队列:内存敏感、流量控制、稳定性要求高的场景数组结构,初始化时传入大小,有界,FIFO,使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
LinkedBlockingQueue 链表型阻塞队列:吞吐量优先、任务量不确定、内存充足场景,链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
DelayQueue 延时队列:缓存淘汰,无界,元素有过期时间,过期的元素才能被取出。
非阻塞队列:
ConcurrentLinkedQueue: 无界,线程安全,高性能,使用CAS操作,无锁实现
ConcurrentLinkedDeque: 可以在两端插入和移除,无界,线程安全
LinkedTransferQueue: 结合了SynchronousQueue和LinkedBlockingQueue的优点,支持"转移"模式:生产者等待消费者接收
-
threadFactory:线程工厂
创建线程的工厂,可以设定线程名、线程编号等。 -
handler:拒绝策略
当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。
JDK默认的拒绝策略有四种:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务。
顶级接口Executor 执行线程的工具 线程池接口ExectorService
- newCachedThreadPool:创建一个根据需要创建新线程的线程池,但是可以重用以前构造的线程。调用execute将重用以前构造的线程,如果没有可用线程,则新建并添加到pool中。终止并移除60s没有被用的线程
- newFixedThreadPool: 创建一个可重用的固定线程的线程池,以共享的无界队列方式来运行。在有可用线程前,任务会等待
- newScheduledThreadPool:可安排在给定延迟后命令或者定期执行
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(newRunnable(){
@Override
public void run() {
System.out.println("延迟三秒");
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable(){
@Override
public void run() {
System.out.println("延迟1 秒后每三秒执行一次");
}
},1,3,TimeUnit.SECONDS);
}
- newSingleThreadExecutor: 返回只有一个线程的线程池,可以在线程死后重新启动一个线程代替原来线程
2 线程池原理
2.1 ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
是原子变量,同时记录线程池状态和线程池线程个数
以32位为例,高三位表示状态,后面表示个数
2.2 状态
Running:接受新任务并处理阻塞队列里任务
shutdown:拒绝新任务但是处理队列任务
stop:拒绝并抛弃阻塞队列任务,同时中断处理任务
tidying:所有任务都执行完,活动线程为0,将要调用terminated方法
terminated:终止状态
- 状态转换
run->shut:显式调用shutdown,隐式调用finalize
run/shut->stop:显式调用shutdownNow
shut->tidy:线程池和任务队列都为空
stop->tidy:线程池为空
tidy->terminated:执行terminated
3.参数选择
核心线程数:
- CPU 密集型任务(如计算密集型处理),通常设置为 CPU核数 + 1,以避免过多线程上下文切换。
- 对于 I/O 密集型任务(如网络请求、数据库操作),线程大部分时间在等待,可以设置得高一些,比如 2 * CPU核数。
最大线程数:这取决于系统资源和任务特性。需要评估系统能承受的最大负载。
队列选择:
- 如果想控制并发量,可以使用有界队列(如ArrayBlockingQueue),当队列满时,会根据拒绝策略处理新任务。
- 如果不想丢弃任务,可以使用无界队列(如LinkedBlockingQueue),但要小心内存溢出风险。
最重要的是,这些参数需要通过压测来最终确定和调整。 我们当时就是通过压测,观察CPU、内存、IO等指标,逐步调整到最优值。