目录
- 线程池
- Java标准库提供的线程池
- 线程池的实现
线程池
线程池和和字符串常量池, 数据库连接池一样, 都是为了提高程序的运行效率, 减少开销。
随着并发程度的提高, 当我们去频繁的创建和销毁线程, 此时程序的开销还是挺大的, 为了进一步提高效率, 就引入了线程池, 程序中所创建的线程都会加载到一个 “池子” 中, 当程序需要使用线程的时候, 可以直接从池里面获取, 用完了就将线程还给池, 这样在多线程的环境中就不用去重复的创建和销毁线程, 从而使程序的运行效率提高, 线程池是管理线程的方式之一.
为什么从线程池中“拿”线程比直接创建线程更加高效?
1.第一点,线程池可以解决线程生命周期的系统开销问题,同时还可以加快响应速度。因为线程池中的线程是可以复用的,我们只用少量的线程去执行大量的任务,这就大大减小了线程生命周期的开销。而且线程通常不是等接到任务后再临时创建,而是已经创建好时刻准备执行任务,这样就消除了线程创建所带来的延迟,提升了响应速度,增强了用户体验。
2.第二点,线程池可以统筹内存和 CPU 的使用,避免资源使用不当。线程池会根据配置和任务数量灵活地控制线程数量,不够的时候就创建,太多的时候就回收,避免线程过多导致内存溢出,或线程太少导致 CPU 资源浪费,达到了一个完美的平衡。
3.第三点,线程池可以统一管理资源。比如线程池可以统一管理任务队列和线程,可以统一开始或结束任务,比单个线程逐一处理任务要更方便、更易于管理,同时也有利于数据统计,比如我们可以很方便地统计出已经执行过的任务的数量。
Java标准库提供的线程池
Java中提供了线程池相关的标准类ThreadPoolExecutor, 也被称作多线程执行器, 该类中的线程包括两类, 一类是核心线程, 另一类是非核心线程, 当核心线程都被占用还不能满足程序任务执行的需求时, 就会启用非核心线程, 直到任务量少了, 随之非核心线程也就会销毁.
jdk8中提供了4个构造方法, 这里主要介绍和理解参数最多的那一个构造方法, 其他构造方法只是基于这里的减少了参数而已.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
表示核心线程数maximumPoolSize
表示最大线程数,就是核心线程数与非核心线程数之和keepAliveTime
非核心线程最长等待新任务的时间, 超过此时间, 该线程就会被销毁unit
上面参数的时间单位.workQueue
线程池的任务队列(阻塞队列), 通过submit方法将任务注册到该队列中threadFactory
线程工厂, 线程创建的方案.handler
拒绝策略, 描述了当线程池任务队列满了, 如果继续添加任务会以什么样的方式处理.
- 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
- 返回值类型为 ExecutorService
- 通过 ExecutorService.submit 可以注册一个任务到线程池中.
-
下面的是其他的几个构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
使用线程池时, 往往使用的是
ExecutorServerce
,ExecutorServerce
是ThreadPoolExecutor
所实现的一个接口, 其中最重要的一个方法是submit
方法, 这个方法能够将任务交给线程池去执行.import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //线程池 //一般认为,纯用户态的操作,效率要比经过内核态处理的操作,要效果更高~~ //线程池里面有什么? //1.先能够描述任务(直接用Runnable) 2.需要组织任务(直接用BlockingQueue) 3.能够描述工作线程 4.还需要组织这些线程 5.需要实现往线程池里添加任务 public class Demo { public static void main(String[] args) { //创建一个固定线程数目的线程池,参数指定了线程个数 ExecutorService pool = Executors.newFixedThreadPool(10); //创建一个自动扩容的线程池,会根据任务量来自动进行扩容 //Executors.newCachedThreadPool(); //创建一个只有一个线程的线程池 //Executors.newSingleThreadExecutor(); //创建一个带有定时器使用的线程池,类似于 Timer //Executors.newScheduledThreadPool(); for (int i = 0; i < 100; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println("Hello threadpool"); } }); } } }
Executors 创建线程池的几种方式
- newFixedThreadPool: 创建固定线程数的线程池
- newCachedThreadPool: 创建线程数目动态增长的线程池.
- newSingleThreadExecutor: 创建只包含单个线程的线程池.
- newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
线程池的实现
- 任务, 可以直接使用`Runnable`实现.
- 组织任务的数据结构, 使用阻塞队列`BlockingQueue`即可.
- 若干个工作线程, 工作线程要通过一个循环不断的从阻塞队列中获取任务.
- 注册任务的方法`submit,` 将任务添加到阻塞队列当中.
import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; class MyThreadPool { //使用阻塞队列来保存任务 private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); //这里创建出若干个工作线程,n表示线程的数量 public MyThreadPool(int n) { for (int i = 0; i < n; i++) { Thread t = new Thread(() -> { while (!Thread.interrupted()) { try { Runnable runnable = queue.take(); runnable.run(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); } } //注册任务给线程池 public void submit(Runnable runnable) { try { queue.put(runnable); } catch (InterruptedException e) { throw new RuntimeException(e); } } }