线程池是什么
线程的诞生是因为进程创建和销毁的成本太大,但是也是相对而言,如果频繁的创建和销毁线程那么这个成本就不能忽略了。
一般有两种方法来进一步提高效率,一种是协程(这里不多做讨论),另一种就是线程池
这样把线程创建好,放在“池子”里,后续用的时候直接从池子里取就好,不用系统进行创建,不用时还是放到池子里,不用系统销毁。那么为什么从池子取的效率就比创建新线程高?因为从池子取这个动作,是纯用户态的操作,而创建新的线程,这个动作则是,需要用户态+内核态互相配合。
线程池的优势
- 线程池最大的好处就是减少每次启动、销毁线程的损耗。
- 当有任务来时,不需要等待新线程的创建,利用已创建的线程就可以执行
- 方便对线程进行统一管理和调度
工厂模式
线程池对象不是我们直接new的,而是专门通过一个方法,返回一个线程池对象,这种设计模式,就叫做工厂模式。
实践中,一般单独搞一个类,给这个类搞一些静态方法,由这些静态方法负责构造出对象
工厂模式详解
线程池的创建
- 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
- 返回值类型为 ExecutorService
- 通过 ExecutorService.submit 可以注册一个任务到线程池中
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors 创建线程池的几种方式
- newFixedThreadPool: 创建固定线程数的线程池
- newCachedThreadPool: 创建线程数目动态增长的线程池.(随着往线程池里添加任务,这个线程池中的线程会根据需要自动被创建出来,创建出来之后也不会着急销毁,而是会在池子里保留一定时间,以备随时使用)
- newSingleThreadExecutor: 创建只包含单个线程的线程池.
- newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
上述几个工厂方法生成的线程池,本质上是 ThreadPoolExecutor 类的封装,这个类功能非常丰富,提供了很多参数,标准库上述的几个工厂方法,其实就是给这个类填写了不同的参数用来构造线程池。
(线程池的真正实现类是ThreadPoolExecutor).
线程池的参数
可见线程池有许多的参数具体如下:
corePoolSize(核心线程数):
maximumPoolSize(最大线程数):
keepAliveTime(线程限制超时时长):
unit(keepAliveTime的单位):
workQueue(工作队列):
threadFactory(线程工厂):
handler(线程池的拒绝策略):
拒绝策略
当线程池的线程数目达到最大线程数时,所执行的策略。 Executors给我们提供了四种常用的拒绝策略。
- ThreadPoolExecutor.AbortPolicy(默认):直接抛出RejectedExecutionException 异常
- ThreadPoolExecutor.CallerRunsPolicy:新添加的任务,由新添加任务的线程负责执行
- ThreadPoolExecutor.DiscarOldestPolicy:丢弃任务队列中最老的元素
- ThreadPoolExecutor.DiscarPolicy:丢弃当前新加的任务
线程数目
在使用线程池时,需要设置线程数目,那么设置多少合适?N?N+1?2N?都不是。
一个线程执行的代码主要有两大类:
- cpu密集型:代码的主要逻辑是在进行算术运算/逻辑判断
- IO密集型:代码主要进行的是IO操作
假设一个线程的所有代码都是cpu密集型,这时线程池的线程数量不应该超过N,设置比N更大时,也无法提高效率了(cpu吃满了)此时更多的线程反而增加了调度的开销.
再假设一个线程的代码都是IO密集型。这时程序不吃cpu,设置的线程数就可以超过N
所以代码不同线程池的线程数目设置就不同,就无法知道一个代码具体内容是cpu聚集多一些,还是IO聚集多以一些。
正确的做法应该是:使用实验的方式,对程序进行性能测试,实验过程中尝试修改不同的线程池线程数目,看看那种情况下最符合要求
实现简单线程池
class MyThreadPool{
//任务队列
private BlockingQueue<Runnable> queue= new ArrayBlockingQueue<>(1);
//通过这个方法,把任务添加到队列中
public void submit(Runnable runnable) throws InterruptedException {
//此处的拒绝策略是阻塞等待
queue.put(runnable);
}
public MyThreadPool(int num){
//创建出n个线程负责执行上述任务
for (int i = 0; i < num; i++) {
Thread t = new Thread(() -> {
//让这个线程从队列中消费任务
try {
while(!Thread.interrupted()){
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(4);
for (int i = 0; i < 100; i++) {
int id = i;
Thread.sleep(500);
myThreadPool.submit(new Runnable() {
@Override
public void run() {
//这里不能使用i因为匿名内部类的变量捕获,类里要使用不变值
System.out.println("id" + id);
}
});
}
}