线程池是什么
- 线程池与字符串常量池一样,也是为了提高效率。
- 虽然线程是轻量的,但是在频繁的创建 / 销毁线程的情况下, 开销是不可忽略的。
- 线程池就是提前创建好线程,需要用到线程时,不再去创建线程,而是使用线程池提前准备好的线程。
从线程池里拿线程为什么比从系统创建线程要更高效?
从线程池里拿线程是纯粹的用户态操作,而从系统创建线程是内核态与用户态之间的切换,操作系统的内核要为很多应用程序提供服务,有的时候服务不一定及时,所以从系统创建线程更低效,时间不可控。
线程池的好处
- 降低资源消耗:减少线程的创建和销毁带来的性能开销。
- 提高响应速度:当任务来时可以直接使用,不用等待线程创建
- 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。
创建线程池
Executor 是一个工厂类, ExecutorService 表示一个线程实例
Executor 类创建线程池的几种方式, 返回值是 ExecutorService 对象
- 创建固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
- 创建线程数目动态增长的线程池
ExecutorService pool = Executors.newCachedThreadPool();
- 创建只包含当个线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
- 设定延时时间后执行命令, 或者定期执行命令, 是进阶版的 Timer (定时器)
ExecutorService pool = Executors.newScheduledThreadPool(10);
- 观察这些创建方式,可以发现创建线程没有直接 new 对象,而是调用 Executors 类里的静态方法来创建线程池。
这是工厂设计模式,是为了解决构造方法的缺陷的(有的场景下,需要构造方法的参数一样,但这是不允许的),不直接 new 对象,
通过工厂方法(一般是静态方法)来协助我们来创建对象。
ThreadPoolExecutor 是原装的线程池类, 上述的工厂方法就是对这个类进行封装
ThreadPoolExecutor 类
构造方法:
参数说明:
corePoolSize
核心线程数 (始终存活的线程)maximumPoolSize
最大线程数 (核心线程数+临时线程数,任务比较多时, 会多创建一些线程, 任务较少时, 会结束一些线程)keepAliveTime
保持存活时间 (任务较少时, 比较空闲, 临时线程保持多久)unit
保持存活时间的单位, ms, sworkQueue
可以手动传一个阻塞队列进去, 线程池里要管理很多任务, 这些任务也是通过队列来组织的threadFactory
创建线程的工厂, 参与具体的创建线程工作(工厂类)handler
线程池的拒接策略, 如果线程池满了, 继续添加任务, 如何拒绝
拒绝策略:
- 抛出一个抛出一个 RejectedExecutionException 异常
ThreadPoolExecutor.AbortPolicy() - 让添加任务的线程自己负责执行这个任务
ThreadPoolExecutor.CallerRunsPolicy() - 丢弃最旧的未处理任务(队首任务)
ThreadPoolExecutor.DiscardOldestPolicy() - 丢弃最新任务(丢弃被拒绝的任务)
ThreadPoolExecutor.DiscardPolicy()
直接使用构造方法 new 一个线程池:
// 自定义创建线程池
ExecutorService pool = new ThreadPoolExecutor(
10, // 核心线程数
100,// 最大线程数
1000,// 最大存活时间
TimeUnit.MILLISECONDS,// 单位
new LinkedBlockingDeque<>(),// 阻塞队列
new ThreadPoolExecutor.AbortPolicy()// 拒绝策略
);
线程池的简单实现
代码:
public class MyThreadPool {
// 阻塞队列存放任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// 添加任务
public void submit(Runnable runnable) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 构造方法, 实现一个固定线程数的线程池
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread thread = new Thread(() -> {
while (true) {
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动线程
thread.start();
}
}
public static void main(String[] args) {
// 创建一个固定10个线程的线程池
MyThreadPool myThreadPool = new MyThreadPool(10);
for (int i = 0; i < 30; i++) {
int n = i;// 实际final
// 向线程池添加任务
myThreadPool.submit(() -> {
System.out.println("hello" + n);
});
}
}
}
执行结果:
- 线程池中的10个线程无序调度, 执行任务顺序和添加任务顺序不一致很正常