最近在学线程池,了解线程池的原理,对以后学习多线程有更好的理解。
目录
- 线程、线程任务的关系
- 为什么要使用线程池
- 线程池的实现原理
- 线程池的类型
- 优化接口实战
线程、线程任务的关系
开启线程,执行任务的流程:
1.创建线程任务;(三种方法)
1.继承Thread类,覆盖重写run()方法,在run方法中填写任务要执行的内容;
2.实现Runnable接口,覆盖重写run()方法,在run方法中填写任务要执行的内容;
3.实行Callable接口,覆盖重写call()方法,在call方法中填写任务要执行的内容;
2.创建Thread类的线程对象;
Thread thread=new Thread();
3.通过线程对象调用start()方法,执行任务体;
thread.start();
为什么要使用线程池
这个问题对所有的池类有相似的解释,例如熟悉的连接池、对象池、内存池、线程池,以线程池为例:
带来的问题:
- 线程大量的创建和销毁,加大了性能的开销,例如每次创建线程,cpu都要做一次内存的分配、调佣内核指令、线程上下文切换。
- 大量的线程无法控制线程的数量,影响cpu的资源。
好处:
- 减少线程的创建,复用线程。
- 可观控制线程的创建数量。
线程池的实现原理
实现线程池要解决的问题:
- 让线程的run方法不执行结束,一直执行。
- 有任务的时候,线程执行。
- 没有任务的时候,线程处于休眠状态,让出cpu.
在设计模式中,选用生产者-消费者模式,使用BlockingQueue阻塞队列作为消费者和生产者的中介,可以对生产者和消费者进行阻塞和唤醒。如下图:
- 生产者将生产出来的任务放入到队列中,消费者在队列中获取任务进行消费。
(main方法为生产者,线程池里的线程为消费者,main方法将任务放到线程池的队列中,线程池里的线程获取任务执行) - 当队列的值达到队列长度时,生产者会被阻塞,等消费者消费了任务时,唤醒生产者。
- 当队列的值为空时,消费者会被阻塞,等生产者生产了任务时,唤醒消费者。
JDK提供的线程池的具体实现,实现类:
java.util.concurrent.ThreadPoolExecutor
构造方法:
public ThreadPoolExecutor(int corePoolSize, //(核心线程数)
int maximumPoolSize,//(最大线程数)
long keepAliveTime, //(存活时间)
TimeUnit unit,//(存活单位)
BlockingQueue<Runnable> workQueue, //(阻塞队列)
ThreadFactory threadFactory, //(创建线程的工厂)
RejectedExecutionHandler handler)//(饱和策略)
通过一个例子引出创建线程池的参数:
1.在main方法中提交一个任务到阻塞队列(阻塞队列)中;
2.线程池中已经创建好的线程(创建线程的工厂)对阻塞队列的任务进行消费(核心线程数);
3.当队列的任务量过大时,导致线程消费不过来,这时候开始克隆新的线程,也就是新建新的线程;
4.当队列满时,新建线程也到达了设定的最大值(最大线程数),当main方法继续加入新的任务是,此时会采取一些措施(饱和策略),例如拒绝消费。
5.当队列的任务书很少时,新建出来的线程会在特定的时间销毁(存活时间、存活单位)。
使用线程池的例子:
/**
* @author libo
* @data 2022/6/1- 23:28
*/
public class PoolDemo1 {
static ThreadPoolExecutor executorPool = new ThreadPoolExecutor(3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int j = i;
String taskName = "任务" + j;
executorPool.execute(()->{
try {
TimeUnit.SECONDS.sleep(j);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(Thread.currentThread().getName() + taskName + "处理完毕");
}
executorPool.shutdown();
}
}
线程池的类型
有四种线程池的类型,可以通过Executors框架创建。
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService1 = Executors.newFixedThreadPool(4);
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
ExecutorService executorService3 = Executors.newScheduledThreadPool(4);
查看每种线程池的构造方法,其实底层都是在创建ThreadPoolExecutor,只是做了一层封装。
ExecutorService executorService = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ExecutorService executorService1 = Executors.newFixedThreadPool(4);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ExecutorService executorService3 = Executors.newScheduledThreadPool(4);
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
优化接口实战
处理业务逻辑时,难免会遇到调用几个mapper层接口获取数据进行处理,如果遇到处理方法耗时大且这几个方法没有关联的,可以尝试使用线程池对接口进行优化。
sevice层:
业务需要统计三个参数的值,分别调用三个mapper方法获取数据,比如数据量大,每个方法耗时5秒,按程序串联执行,这需要15秒完成操作。
//耗时6秒
Map<String, Object> user = statMapper.getUser(wallet);
//耗时5秒
Map<String, Object> amount = statMapper.getAmount(wallet);
//耗时4秒
Map<String, Object> allLog = statMapper.getLog(wallet);
for (String key : user .keySet()) {
activity.setUser((Long) user.get(key));
}
for (String key : amount .keySet()) {
activity.setAmount((BigDecimal) amount.get(key));
}
for (String key : allLog .keySet()) {
activity.getLog((Long) allLog .get(key));
}
以上串行处理完程序需要6+5+4=15秒,现在改用线程池来优化接口,使这几个接口异步进行:
//创建线程池,默认值赋予3
ExecutorService executorService = Executors.newFixedThreadPool(3);
//创建CompletionService接口的实现类,用来处理异步的线程,并且接收返回的结果
CompletionService completionService = new ExecutorCompletionService<Object>(executorService);
//耗时6秒
completionService.submit(() -> statMapper.getUser(wallet));
//耗时5秒
completionService.submit(() -> statMapper.getAmount(wallet));
//耗时4秒
completionService.submit(() -> statMapper.getLog(wallet));
executorService.shutdown();
for (int i = 0; i < 3; i++) {
//阻塞式获取线程执行完毕的结果
Map<String, Object> map = (Map<String, Object>) completionService.take().get();
for (String key : map.keySet()) {
switch (key) {
case "user":
activity.setUser((Long) map.get(key));
break;
case "amount":
activity.setAmount((BigDecimal) map.get(key));
break;
case "allLog":
activity.setLog((Long) map.get(key));
break;
}
}
}
由于程序异步执行,所以只用时6秒就可以完成操作。