前言
多线程的出现是为了优化多进程,优化进程创建、销毁的开销;而随着计算机技术的发展,人们又进一步优化多线程,提前创建线程减少多线程的开销,让程序更快更轻量。于是线程池诞生了,根据需求提前创建一堆线程,里面的线程也可以复用,减少创建销毁的内存开销,将创建和使用线程的步骤分开既减少了代码耦合度,也方便统一管理线程。线程池的实现(依靠ThreadPoolExecutor)开发者也是可以自定义的,包括池里线程的数量、池满的策略、单个线程的具体属性。
实现线程池的两种方式
Executor将线程池这一概念抽象出来,ExecutorService继承Executor接口,抽象类AbstractExecutorService类实现ExecutorService接口,ThreadPoolExecutor继承AbstractExecutorService,而最终通过ThreadPoolExecutor来实现线程池。
Java可以通过Executors工厂类的静态方法和ThreadPoolExecutor实现线程池。ThreadPoolExecutor高度自由化,可以通过最多7个参数来自定义线程池,而Executors是对ThreadPoolExecutor的封装,Executors实现了几种常见的线程池,并对外提供了静态方法用于获取线程池实例。所以线程池的最底层就是ThreadPoolExecutor类。
ThreadPoolExecutor
“任务”概念:Runnable类的run()方法主体内容
//构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){}
参数名 | 定义及定位 |
---|---|
corePoolSize | 核心线程数(最小线程数);核心线程即便空闲也不会自动销毁,除非主动销毁或线程池关闭 |
maximumPoolSize | 最大线程数(包括核心线程和非核心线程);非核心线程根据任务需要被创建,空闲超时(keepAliveTime)则被回收销毁 |
keepAliveTime | 非核心线程最大空闲时间 |
TimeUnit unit | 枚举变量;定义空闲时间的时间单位 |
BlockingQueue workQueue | 工作阻塞队列;用于存放一系列任务并取出任务给线程池里的线程执行 |
ThreadFactory threadFactory | 工厂类;一些类对Thread类的创建进行封装(自定义线程名、优化级等)达到构造不同属性的线程的目的,传入这些类可以让线程池里存在不同属性的线程 |
RejectedExecutionHandler handler | 拒绝策略;用于处理线程池满、工作队列满的情况,默认提供了四种策略,开发者也可以自定义实现策略类 |
当向线程池提交一个任务时触发了拒绝策略:
四种拒绝策略 | 内容 |
---|---|
AbortPolicy() | 抛出异常,让用户来处理触发策略的这种情况 |
CallerRunsPolicy() | 把这个新任务丢给调用submit()(添加这个任务)的线程处理 |
DiscardOldestPolicy() | 丢弃队列里存在最久的任务(队首元素)并让这个新任务替换它 |
DiscardPolicy() | 丢弃这个新来的任务,不管它 |
为了方便理解线程池的逻辑,这里模拟了一个简化版线程池方便理解:
import java.util.concurrent.ArrayBlockingQueue;
class MyThreadPool{
private ArrayBlockingQueue<Runnable> queue=null;
//n为核心线程数
public MyThreadPool(int n){
queue=new ArrayBlockingQueue(100);//队列最多任务数
for (int i = 0; i < n; i++) {
//内部定义一个线程来取出队列里的任务并执行
Thread t = new Thread(() -> {
try {
while (true) {
Runnable task = queue.take();
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
//提交任务到线程池里也就是工作队列里
public void submit(Runnable task) throws InterruptedException {
queue.put(task);
}
}
class DemoThreadPool {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool=new MyThreadPool(100);
for (int i = 0; i < 100; i++) {
int id=i;
pool.submit(()->{
System.out.println(Thread.currentThread().getName()+" id="+id);
});
}
}
}
结果:
在这里插入代码片
通过Executors静态方法
通过Executors静态方法创建线程池,以下方法返回值类型ExecutorService(实际上返回了一个实现ExecutorService接口的类的实例)(newScheduledThreadPool除外,返回值类型是ScheduledExecutorService(实际上返回了一个实现ExecutorService接口的类的实例),ScheduledExecutorService也是一个接口,继承了ExecutorService接口)。
方法名 | 作用 |
---|---|
Executors.newFixedThreadPool() | 创建一个固定线程数目的池 |
Executors.newCachedThreadPool() | 创建一个线程数动态增加的池 |
Executors.newSingleThreadExecutor() | 创建一个只有一个线程的池 |
Executors.newScheduledThreadPool() | 相较于普通线程池,它可以控制任务执行时间,让任务定时或延时执行 |
使用newFixedThreadPool()替代上面的示例,效果类似:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService pool= Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
int j=i;
//提交一个匿名Runnable类对象
pool.submit(()->{
System.out.println(Thread.currentThread().getName()+" i= "+j);
});
}
}
}
Executors创建线程池的缺陷
由于Executors静态方法返回的是一个现成的线程池,它不能百分百的适配实际的开发需求和任务需要。例如:
- newFixedThreadPool()和newSingleThreadExecutor()里的工作队列长度允许最大为Integer.MAX_VALUE,很有可能会被添加过多的任务,导致内存溢出;
- newCachedThreadPool()和newScheduledThreadPool(),允许最大创建Integer.MAX_VALUE个线程,可能被发生大量线程被创建的情况,导致内存溢出。
因此,最好使用ThreadPoolExecutor来创建线程池,这样线程数目、队列满情况处理、拒绝策略等也是可控的。
最后,线程池里的线程都是前台线程,因此,上面两个示例程序在100层for循环任务结束后程序并未结束而是进入WATTING状态,线程池Running。想结束线程池,可以使用线程池对象调用三种方法:
方法名 | 作用 |
---|---|
shutdown() | 将队列里的任务执行完后再关闭线程池,在此期间线程池不再接收新任务(比下面的更优雅);线程池先SHUTDOWN 再STOP |
shutdownNow() | 尝试中断正在执行的任务(是否响应中断取决于任务自己的逻辑),将余下的任务以链表形式返回(List< Runnable >);线程池STOP |
awaitTermination(long timeout, TimeUnit unit) | 设定超时时间,若超时则返回false,否则返回true。方法本身没有上面两个的关闭线程池作用,通常用来确认线程池是否关闭 |
完。