java异步-线程、线程池
1、java初始化线程的4种方式
- 继承Thread,重写run方法
- 实现Runnable接口
- 实现Callable接口 + FutureTask(可以获取线程返回接口,可以处理异常)
- 线程池
-
无论是哪种方式,都需要使用new Thread来开启线程
2、4种实现案例
2.1、继承Thread方式
3、实现Runnable接口
2.2、实现Callable接口
- 实现Callable接口,指定泛型
- 重写call方法
- 在使用时,new FutureTask时传递callable实现类对象
- 由于FutureTask底层使用的是runnable接口,因此new Thread对象,把FutureTask对象传入即可
当FutureTask对象需要获取线程的返回数据时:
- 线程阻塞等待的方式获取结果:与多线程初衷相违背
2.3、线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author 线程池使用
* @date 2023/7/7
*/
public class ExecutorsPool {
/**
* 在java JUC下,提供了一个类Executors,快速创建一个线程池
* 下面创建一个固定数据为10的线程池
*/
public static ExecutorService executors = Executors.newFixedThreadPool(10);
//测试案例
public static void main(String[] args) {
System.out.println("主方法..................开始执行");
//executors.submit(new Runnable() {
// @Override
// public void run() {
//
// }
//})
/** 下面是上面的lambda写法 */
executors.submit(() -> {
System.out.println("当前线程编号为:" + Thread.currentThread().getContextClassLoader());
int a = 10 / 5;
System.out.println("线程计算结果为:" + a);
});
System.out.println("主方法..................结束执行");
}
}
结果如下:
注意:
Executors.submit()与Executors.execute()区别:
- submit方法返回类型为Future<T>,可以获取线程结果
- execute方法返回类型为void,不能获取线程结果
3、线程池
3.1、原始创建方式
- ThreadPoolExecutor executor = new ThreadPoolExecutor();
- 传递线程的七大参数即可
- ThreadPoolExecutor与2.4中的Executors,其底层都是实现了ExecutorService
3.2、线程的七大参数
<1> corePoolSize
核心线程数:
- 线程池创建后一直存在,直至线程池销毁才消失
- 线程池创建好后,就准备就绪了的线程数量,等待接收异步任务去执行
例如: corePoolSize = 5 线程创建后,线程池中有5个线程Thread随时处理业务start()
<2>maximumPoolSize
最大线程数量:
- 最大线程数量,用于控制资源
<3>heepAliverTime
存活时间:
- 当前线程数量大于核心线程数量,空闲的线程超过存活时间时,释放多余的空闲线程
- 这里的空闲线程指的是:该线程业务处理完毕,正在等待新的多线程业务,这个时候的线程是空闲的
- 释放的线程:由于存在核心线程,因此,释放的空闲线程为:当前线程数量-核心线程数量
<4>TimeUnit unit
存活时间单位
<5>BlockingQueue<Runnable> workQueue
阻塞队列:
- 如果多线程任务很多,就会将目前多余的任务放在队列中
- 只要有线程空闲了,就会从队列中取出新的任务进行执行
<6>threadFactory
线程的创建工厂
<7>RejectedExecutionHandler handler
处理器:如果队列满了,按我们指定的拒绝策略拒绝执行任务(饱和策略)
3.3、案例
线程池工作顺序
- 线程池创建后,准备好core核心线程数量,接收多线程任务
- core线程满了后,就将正在进来的任务放入阻塞队列中,空闲的core就会自己去阻塞队列中获取任务执行
- 阻塞队列满后,就直接开启新的线程进行执行任务,最大只能开到max指定的数量
- max满了就用handler拒绝策略拒绝任务
- max都执行完成后,若存在很多空闲线程,在指定的时间keepAliveTime后,释放多余的线程
面试案例
- 一个线程core 7;max 20;queue 50;100并发进行怎么分配的?
- 7个并发会立即得到执行
- 50个会进入队列
- 再开13个进行执行,这里执行的是队列中的13个!(先进先出和后进先出,主要看使用的队列使用哪种)
- 剩下的30个就使用拒绝策略,如:使用CallerRunsPolicy拒绝策略,来让剩下的并发同步执行
如:
import java.util.concurrent.*;
public class ThreadPoolTest {
public static void main(String[] args) {
System.out.println("线程池创建......");
ThreadPoolExecutor executor = new ThreadPoolExecutor(
7
, 20
, 10
, TimeUnit.SECONDS
, new LinkedBlockingDeque<>(50)
, Executors.defaultThreadFactory()
, new ThreadPoolExecutor.AbortPolicy()
);
//模拟100个并发
for (int i = 0; i < 100; i++) {
int num = i;
executor.execute(()->{
System.out.println(Thread.currentThread().getName() + "is executing:" + num);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
结果:
线程池创建......
pool-1-thread-1is executing:0
pool-1-thread-5is executing:4
pool-1-thread-7is executing:6
pool-1-thread-6is executing:5
pool-1-thread-3is executing:2
pool-1-thread-2is executing:1
pool-1-thread-4is executing:3
pool-1-thread-8is executing:57
pool-1-thread-9is executing:58
pool-1-thread-11is executing:60
pool-1-thread-10is executing:59
pool-1-thread-13is executing:62
pool-1-thread-12is executing:61
pool-1-thread-14is executing:63
pool-1-thread-17is executing:66
pool-1-thread-15is executing:64
pool-1-thread-16is executing:65
pool-1-thread-19is executing:68
pool-1-thread-18is executing:67
pool-1-thread-20is executing:69
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.shuizhu.helloworld.thread.ThreadPoolTest$$Lambda$1/1879492184@357246de rejected from java.util.concurrent.ThreadPoolExecutor@2a18f23c[Running, pool size = 20, active threads = 20, queued tasks = 50, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at com.shuizhu.helloworld.thread.ThreadPoolTest.main(ThreadPoolTest.java:20)
pool-1-thread-1is executing:7
pool-1-thread-16is executing:10
pool-1-thread-4is executing:11
pool-1-thread-14is executing:9
pool-1-thread-13is executing:8
pool-1-thread-19is executing:13
pool-1-thread-5is executing:12
pool-1-thread-15is executing:15
pool-1-thread-10is executing:17
pool-1-thread-8is executing:19
pool-1-thread-2is executing:22
pool-1-thread-17is executing:14
pool-1-thread-18is executing:25
pool-1-thread-20is executing:26
pool-1-thread-9is executing:24
pool-1-thread-7is executing:20
pool-1-thread-11is executing:23
pool-1-thread-6is executing:21
pool-1-thread-3is executing:18
pool-1-thread-12is executing:16
pool-1-thread-1is executing:27
pool-1-thread-14is executing:31
pool-1-thread-4is executing:30
pool-1-thread-16is executing:29
pool-1-thread-13is executing:28
pool-1-thread-20is executing:32
pool-1-thread-18is executing:37
pool-1-thread-17is executing:40
pool-1-thread-8is executing:36
pool-1-thread-15is executing:35
pool-1-thread-19is executing:33
pool-1-thread-5is executing:34
pool-1-thread-2is executing:39
pool-1-thread-10is executing:38
pool-1-thread-3is executing:41
pool-1-thread-11is executing:43
pool-1-thread-6is executing:42
pool-1-thread-9is executing:46
pool-1-thread-12is executing:44
pool-1-thread-7is executing:45
拒绝策略
- DiscardOldestPolicy
丢弃还没有执行的旧的任务(队列最前面的是最旧的),执行新的任务
- CallerRunsPolicy
多余的线程直接执行调用run方法,相当于同步执行
- AbortPolicy(默认的拒绝策略)
新来的任务直接丢弃,并会抛出异常
- DiscardPolicy
新来的任务直接丢弃,不会抛出异常
3.4、Executors创建线程池
上述的线程池创建是原生的方式
也可以使用Executors来创建线程池
1、Executors.newCachedThreadPool();
- core核心线程为0,最大线程数为Integer.MAX_VALUE
- 创建一个可以缓存线程的线程池( 由于核心线程为0,缓存时间为存活时间60s),可以灵活回收空闲线程
2、Executors.newFixedThreadPool(10);
- 创建一个固定线程数量的线程池
- 核心线程=最大线程,因此线程是一直存活的
- 可以控制线程最大并发数,超出的线程会在队列中等待
3、Executors.newScheduledThreadPool(10);
- 创建一个定长的线程池,支持定时及周期性任务执行
- 可以指定多长时间后再执行
4、Executors.newSingleThreadExecutor();
- 创建一个单线程化的线程池,它只用唯一的工作线程来执行任务,保证所有任务执行完成
- 适用场景:分布式下,保证双写一致的情况,需要将相同的业务留给同一台服务器或者同一线程来处理