1 前言
这节开始我们主要是攻克一下Java中的线程池,来深入的分析一下线程池是怎么设计的,线程池的原理是什么等,只有更好的理解原理,才能很好的使用并且规避掉一些问题,那么本节我们先简单介绍下线程池是什么以及平时大家的使用方法。
2 线程池的使用
大都是为了提升效率,并发的做某些事情;或者是将一个任务拆分成多个小任务,每个小任务都交给线程池去完成,之后将每个任务的结果合并。
或者是一些异步的场景,将任务直接交给线程池去做...等等,使用的场景非常多。先举个例子,我们是怎么使用线程池的:
public class ThreadPoolTest { public static ExecutorService createThreadPool() { // 创建一个线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( // 核心线程数设置为2 2, // 最大线程数设置为5 5, // 当线程池中的线程数量大于2,这多出的线程在没有任务执行的最大空闲时间,60 60, // 空闲时间的单位,这里设置秒 TimeUnit.SECONDS, // 线程池的阻塞队列使用LinkedBlockingQueue无界阻塞队列 new LinkedBlockingQueue(), // 创建线程的线程工厂,这里test-pool是设置这个线程池的名字 new DefaultThreadFactory("test-pool"), // 当任务太多,线程池线程不足、阻塞队列满时候采取什么拒绝策略 new ThreadPoolExecutor.CallerRunsPolicy() ); // 返回创建的线程池 return executor; } static class DefaultThreadFactory implements ThreadFactory { private String name; private final AtomicInteger nextId = new AtomicInteger(); DefaultThreadFactory(String name) { this.name = name; } @Override public Thread newThread(Runnable r) { return new Thread(null, r, name + "-" + nextId.incrementAndGet()); } } public static void main(String[] args) { // 创建一个线程池 ExecutorService executor = createThreadPool(); for (int i = 0; i < 1000; i++) { final int num = i; // 封装任务,每个任务就是打印自己是当前第几个号 Runnable task = () -> { System.out.println(num); }; // 往线程池提交任务 executor.execute(task); } } }
上面就是一个线程池使用的小例子:
(1)createThreadPool方法创建出来一个线程池。
(2)然后有1000个任务要跑,每个任务要做的事就是打印自己当前是第几号
(3)封装1000个任务,调用execute方法提交1000个任务
上面的例子是ThreadPoolExecutor线程池,这种线程池是提交任务之后,如果有线程资源空闲会立即执行你提交的任务。
但是其实Java提供的还有另外一种线程池,支持任务的延迟调度、或者任务的周期性的调度的线程池,叫做ScheduledExecutorService,它的使用例子如下:
public class ScheduledThreadPoolTest { // 创建一个调度线程池 public static ScheduledExecutorService createThreadPool() { ScheduledExecutorService executor = new ScheduledThreadPoolExecutor( // 线程池的核心线程数,配置为3 3, // 创建线程的工厂,test-schedule-pool为线程池的名称 new DefaultThreadFactory("test-schedule-pool"), // 任务拒绝策略,线程资源不足的时候,策略是“使用调用线程池的线程来执行” new ThreadPoolExecutor.CallerRunsPolicy() ); return executor; } static class DefaultThreadFactory implements ThreadFactory { private String name; private final AtomicInteger nextId = new AtomicInteger(); DefaultThreadFactory(String name) { this.name = name; } @Override public Thread newThread(Runnable r) { return new Thread(null, r, name + "-" + nextId.incrementAndGet()); } } public static void main(String[] args) { // 获取调度线程池 ScheduledExecutorService executor = createThreadPool(); // 打印当前时间 System.out.println("当前时间:" + new Date()); // 立即执行的任务 Runnable nowTask = () -> { System.out.println("当前时间:" + new Date() + "------执行【立即】任务"); }; // 延迟执行的任务 Runnable delayTask = () -> { System.out.println("当前时间:" + new Date() + "------执行【延迟】任务"); }; // 周期性执行的任务 Runnable periodTask = () -> { System.out.println("当前时间:" + new Date() + "------执行【周期性】任务"); }; // 提交立即任务,有线程空闲立即执行 executor.execute(nowTask); // 提交一次性延迟任务,延迟2秒执行 executor.schedule(delayTask, 2, TimeUnit.SECONDS); // 提交周期性的延迟任务,10秒后每3秒执行一次 executor.scheduleWithFixedDelay(periodTask, 10, 3, TimeUnit.SECONDS); } }

如上图所示,任务的提交时间都是在07:33:39这个时间
(1)其中【立即】任务调用execute方法的任务立即执行了,事实上ScheduleExecutorService继承了ExecutorService接口,具有和他一样的功能,也就是说具有提交任务,线程空闲时立即执行的功能
(2)同时看【延迟】任务,在2秒之后执行了一次,说明具有延迟任务的功能
(3)【周期性】任务,每隔3秒执行一次,说明具有定时任务的功能,是一个定时调度器
上面就是Java提供的两种类型的线程池了,一种是ExecutorService类型的线程池,代表性的子类是ThreadPoolExecutor;
另外一种是ScheduledExecutorService调度类型的线程池,代表性的子类是ScheduledThreadPoolExecutor。
同时JDK为了我们平时线程池的使用方便,提供了一个工具类Executors,里面封装了这两个类型的线程池的创建方法,如下:
public class ExecutorsTest { public static void main(String[] args) { // 创建一个缓存型的线程池,这种线程池每来一个任务就会创建一个线程来执行 Executors.newCachedThreadPool(); // 创建一个固定线程数量的线程池 Executors.newFixedThreadPool(3); // 创建一个单线程的线程池 Executors.newSingleThreadExecutor(); // 创建一个单线程的调度线程池 Executors.newSingleThreadScheduledExecutor(); // 创建一个固定线程数量的调度线程池 Executors.newScheduledThreadPool(3); } }
如上代码所示:Executors这个工具类里面封装了蛮多创建线程的方法,来简化我们平时对线程池的创建。
但是平时我们一般是不用Executors这个工具类来创建线程池的,因为Executors里面创建的线程池,参数设置得很不合理,直接使用可能会有很大问题。
3 小结
本节我们简单先回顾下我们平时线程池的使用,下节我们会开始看线程池的实现细节,有理解不对的地方欢迎指正哈。