线程池
为什么要使用线程池?
在没有使用线程池之前,是这样执行任务的
一个线程只能执行一个任务,不能连续执行任务
public class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); }}public class Main { public static void main(String[] args) { Task task = new Task(); Thread thread = new Thread(task); thread.start(); }}
这暴露了一个问题,线程不能复用,重复创建和销毁线程耗时耗资源,若能复用就好了,复用的好处就是省时省资源
看线程池如何执行⤵️
创建只有一个线程的线程池,这个线程池中只有一个线程,重点是它里面的线程可以复用
public class Main { public static void main(String[] args) { Runnable task1 = new Task(); Runnable task2 = new Task(); Runnable task3 = new Task();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); threadPool.execute(task1); threadPool.execute(task2); threadPool.execute(task3);
threadPool.shutdown(); }}说明一个线程执行了3个任务

线程池的好处
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度:当有任务时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,线程池可以进行统一的分配,调优和监控
什么是线程池
线程池是一种基于池化思想管理线程的工具
在没有线程池之前,当有任务需要执行时,我们会创建一个线程,然后将任务传递给线程,并且一个线程只能执行一个任务,如果还有任务,我们就只能再创建一个线程去执行它,当任务执行完时,线程就销毁了,重复创建和销毁线程是一件很耗时耗资源的事,如果线程能复用,那么就减少很多不必要的消耗,于是线程池就孕育而生了,事先将线程创建好,当有任务需要执行时,提交给线程池,线程池分配线程去执行,有再多的任务也不怕线程池中的线程能复用,执行完一个任务,再接着执行其他任务,当所有任务都执行完时,我们可以选择关闭线程池,也可以选择等待接收任务。
原生方式创建线程池
线程池核心UML类图

ThreadPoolExecutor是线程池核心类,它一共有四个构造方法

最复杂的说起,它一共有7个参数⤵️
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)参数含义:
| 参数 | 描述 |
|---|---|
| corePoolSize | 核心线程数 |
| maximumPoolSize | 最大线程数 |
| keepAliveTime | 空闲线程存活时间 |
| unit | 时间单位 |
| workQueue | 任务队列 |
| threadFactory | 线程工厂 |
| handler | 拒绝任务策略 |
参数解析:

核心线程的好处:只要线程池不关闭,就不会被销毁
最大线程数:表示线程池中最多允许有25个线程
除去核心线程之外的线程是非核心线程,非核心线程没有执行任务的话,是要被清理的,在被清理之前能存活多久取决于第三个和第四个参数; 当任务都执行完以后,所有线程都成了空闲线程,还是要分核心与非核心线程,再过十秒,若非核心线程没有工作,就要被销毁,剩下的都是核心线程
workQueue是任务队列,线程池中的线程们也都是在这领取的任务,我一般用的是 LinkedBlockingQueue链式阻塞队列,基于链表的阻塞队列;还有一个常用的是ArrayBlockingQueue数组阻塞队列,这是一个基于数组的阻塞队列
threadFactory:线程工厂,指定线程该如何生产,它是一个接口,实现它里面的 newThread 方法,可以自定义线程的相关设置; 例如:可以指定线程名称,还可以指定是否为后台线程
public interface ThreadFactory {
/** * 构造一个新线程,可以指定线程名称、优先级等等 * * @param r 新线程执行的任务 * @return 构造的新线程,如果创建线程的请求被拒绝,则为 nulL */ Thread newThread(Runnable r);}
如果你不想自定义线程工厂,那么可以使用,Executors类中的默认线程工程

handler:任务拒绝策略,什么情况下我们提交给线程池的任务会被拒绝呢,要满足以下四种情况
- 线程池中线程已满
- 无法继续扩容
- 没有空闲线程,所有线程都在执行任务
- 任务队列已满,无法再存入新任务
同时满足这四种情况时,我们提交给线程池的任务才会被拒绝
线程池拒绝我们的方式也有四种:

示例:
自定义线程工厂
public class CustomThreadFactory implements ThreadFactory { private final AtomicInteger i = new AtomicInteger(1);
@Override public Thread newThread(Runnable r) { // 创建线程,执行任务 Thread thread = new Thread(r); // 设置线程名称 thread.setName("线程" + i.getAndIncrement() + "号"); // 返回线程 return thread; }}public class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); }}public class Main { public static void main(String[] args) { // 创建任务 Runnable task1 = new Task(); Runnable task2 = new Task(); Runnable task3 = new Task(); // 创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 25, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new CustomThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); // 提交任务 threadPoolExecutor.execute(task1); threadPoolExecutor.execute(task2); threadPoolExecutor.execute(task3); // 关闭线程池 threadPoolExecutor.shutdown(); }}
这 3 种创建线程池的方式有风险
分别是
- 固定大小线程池
FixedThreadPool - 单个线程的线程池
SingleThreadExecuton - 可缓存的线程池
CachedThreadPool
这三种创建方式都在Executors 工具类中

所有已new开头的都能创建线程池
一共有12个这样的方法,去掉重载方法后,就剩下6个

FixedThreadPool

FixedThreadPool,它内部采用 ThreadPoolExecutor来创建线程池,核心线程数和最大线程数一样,意味着它里面全是核心线程;空闲线程存活时间为0毫秒,这样空闲线程就不会被销毁,任务队列采用的是 LinkedBlockingQueue;
此队列有资源耗尽的风险,因为LinkedBlockingQueue的容量为Interger的最大值,Interger的最大值是231,意味着任务队列中的任务数量可高达 21亿之多,内存随时都有可能爆掉,不推荐使用 FixedThreadPool;

在《阿里巴巴Java开发手册》的第一章第7小节,第4条中这样写道⤵️

创建固定大小的线程池有两个方法⤵️
演示一个参数的方法:
public class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); }}public class Main { public static void main(String[] args) { // 创建任务 Runnable task1 = new Task(); Runnable task2 = new Task(); Runnable task3 = new Task(); // 创建线程池 ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(10); // 提交任务 threadPoolExecutor.execute(task1); threadPoolExecutor.execute(task2); threadPoolExecutor.execute(task3); // 关闭线程池 threadPoolExecutor.shutdown(); }运行结果

SingleThreadExecutor
它内部也采用 ThreadPoolExecutor来创建线程池,核心线程数和最大线程数一样,意味着它里面全也都是核心线程,只不过只有一个,空闲线程存活时间为0毫秒,它里面的空闲线程也不会被销毁,任务队列采用的是 LinkedBlockingQueue,风险和FixedThreadPool一样的问题,不推荐使用 SingleThreadExecutor

方法:

public class Main { public static void main(String[] args) { // 创建任务 Runnable task1 = new Task(); Runnable task2 = new Task(); Runnable task3 = new Task(); // 创建线程池 ExecutorService threadPoolExecutor = Executors.newSingleThreadExecutor(); // 提交任务 threadPoolExecutor.execute(task1); threadPoolExecutor.execute(task2); threadPoolExecutor.execute(task3); // 关闭线程池 threadPoolExecutor.shutdown(); }程序输出三个一样的线程名称,说明线程池中的确只有一个线程

CachedThreadPool

可缓存的线程池,可缓存的意思是,它里面除了核心线程以外,还有非核心线程,他内部也采用了ThreadPoolExecutor来创建线程池,核心线程数是0,意味着可缓存的线程池里面全都是非核心线程,最大线程数是 Integer的最大值,风险不言而喻,弊端就不再赘述了,和前面两个线程池的问题一样,不推荐使用 CachedThreadPool,空闲线程存活时间为 60 秒,空闲线程 60 秒内没工作就会被销毁,任务队列采用的是 SynchronousQueue,这是一个同步队列。

创建可缓存的线程池有两个方法

public class Main { public static void main(String[] args) { // 创建任务 Runnable task1 = new Task(); Runnable task2 = new Task(); Runnable task3 = new Task(); // 创建线程池 ExecutorService threadPoolExecutor = Executors.newCachedThreadPool(); // 提交任务 threadPoolExecutor.execute(task1); threadPoolExecutor.execute(task2); threadPoolExecutor.execute(task3); // 关闭线程池 threadPoolExecutor.shutdown(); }程序输出三个不一样的线程名称

总结
FixedThreadPool:固定大小的线程池SingleThreadExecutor:单个线程的线程池CachedThreadPool:可缓存的线程池FixedThreadPool和SingleThreadExecutor允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致00M。CachedThreadPool允许的创建线程数量为Integer.MAX_VALUE可能会创建大量的线程,从而导致 00M。
execute与submit
execute
execute位于Executor接口中,作用是向线程池中提交Runnable任务,Runnable是无返回值的任务,也就是它执行完是没有结果返回给你的,所以,execute 只适合提交无返回值的任务,如果你的任务是有返回结果的,那么你就要创建Callable任务,它是一个有返回值的任务,Callable任务执行完,会将任务执行结果封装到 Future 对象中,然后返回给调用者,调用者再通过 Future 对象获取结果。

public class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); }}public class Main { public static void main(String[] args) { Runnable task = new Task();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); threadPool.execute(task);
threadPool.shutdown(); }程序只输出了一个线程名称

submit

该方法位于 ExecutorService 接口中,一共有三个 submit 方法,他们的作用稍有不同,我将三个方法的作用分别列举出来

它们的返回值类型都是 Future 类型,而且都带泛型,任务执行结果就封装在 Future 对象里面,Future是一个接口,该接口定义了与任务执行结果相关的功能

这是 Future 的 UML类图,他一共有5个可用的方法,这5个方法的作用如图所示


回到submit本身

第一个方法,它的作用是提交 Runnable 任多,submit 方法也可以提交 Runnable 任务,方法返回一个 Future 对象,都无返回值了,为什么还有返回Future对象呢?因为 Future 除了获取任务执行结果以外,还可以观察任务是否执行完毕,以及取消任务,等等操作,所以,Future 对象你可以选择接收,你也可以选择不接收,我们来演示:
public class Main { public static void main(String[] args) { Runnable task = new Task();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 提交任务 Future<?> future = threadPool.submit(task); try { // 获取任务执行结果 Object result = future.get(); // 输出任务执行结果 System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { // 关闭线程池 threadPool.shutdown(); } }null是我们获取的任务执行结果,因为我们提交的是无返回值任务,所以结果为null,如果我们非要给无返回值任务一个结果,是否可以?那也是可以的,这就是我们要介绍第二个 submit 方法

第二个方法,它的作用就是提交一个 Runnable 任务给线程池,并且还可以附带一个执行结果,别的任务都是执行完才知道结果,这个 submit 方法是执行任务之前,都已经知道了任务执行结果,所以,它只适用于,执行任务的同时还要附带一个参数的场景,该方法依然是返回一个 Futrue 对象,这个 Futrue 对象里面装的结果,就是我们刚刚传递的第二个参数;示例:
public class Main { public static void main(String[] args) { Runnable task = new Task();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 提交任务 Future<String> future = threadPool.submit(task, "任务完成"); try { // 获取任务执行结果 String result = future.get(); // 输出任务执行结果 System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { // 关闭线程池 threadPool.shutdown(); } }
再介绍最后一个 submit 方法,它的作用是提交 Callable 任务,也就是有返回值的任务,方法是返回一个 Futrue 对象,示例:
public class ResultTask implements Callable<Integer> { @Override public Integer call() throws Exception { return 1 + 1; }}public class Main { public static void main(String[] args) { ResultTask task = new ResultTask();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 提交任务 Future<Integer> future = threadPool.submit(task); try { // 获取任务执行结果 Integer result = future.get(); // 输出任务执行结果 System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { // 关闭线程池 threadPool.shutdown(); } }符合预期

execute与submit 的区别

总结

评论区
评论区加载中...
如果长时间无法显示,请尝试刷新页面。