54、线程执行框架
本节我们来讲线程执行框架,线程执行框架提供了一系列类,封装了线程:创建、关闭、执行、管理等跟业务逻辑无关的代码逻辑
- 实现了业务逻辑与非业务逻辑的解耦
- 方便代码复用,开发者不再需要编写创建线程、启动线程等代码
上一节中讲到的 ThreadPoolExecutor、Executors 就隶属于线程执行框架
在开始本节的正式内容之前,我还是给你出一道思考题:如何获取一个线程所执行的代码的运行结果?
1、Executor、ExecutorService、Executors
线程执行框架包含三个长相类似的类:Executor、ExecutorService、Executors,很多人分不清楚它们的区别
实际上 Executor 和 ExecutorService 都是接口
- Executor 接口只提供了一个函数:execute()
- ExecutorService 接口对 Executor 接口进行了扩展,额外提供了很多其他函数,包括 submit()、shutdown()、shutdownNow()、awaitTermination() 等等
- JUC 提供的现成的执行器并不多,前面讲到的 ThreadPoolExecutor 便是其中最常用的 一个
我们也可以基于 Executor 接口或 ExecutorService 接口自定义执行器
Executors 类是一个工具类,用来创建各种执行器,有点类似容器中的的 Collections 工具类
ThreadPoolExecutor 在上一节中已经讲过,Executor、ExecutorService 只是接口,比较简单,因此我们重点看下 Executors,特别是其中的几个工厂方法
1.1、newFixedThreadPool()
newFixedThreadPool() 函数用来创建大小固定的线程池,其代码实现如下所示
ThreadPoolExecutor 中的 maximumPoolSize 跟 corePoolSize 相等,因此线程池中的线程都是核心线程,一旦创建便不会销毁
workQueue 为 LinkedBlockingQueue,默认大小为 Integer.MAX_VALUE,相当于无界阻塞队列,任务可以无限的往 workQueue 中提交,永远都不会触发拒绝策略
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() ); }
1.2、newSingleThreadExecutor()
newSingleThreadExecutor() 函数用来创建单线程执行器,其代码实现如下所示
这个函数返回的是大小固定为 1 的 ThreadPoolExecutor 对象,workQueue 同样是大小为 Integer.MAX_VALUE 的 LinkedBlockingQueue
public static ExecutorService newSingleThreadExecutor() { return new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() ); }
1.3、newCachedThreadPool()
newCachedThreadPool() 函数创建的线程池只包含非核心线程,线程空闲 60 秒以上便会销毁
因为 workQueue 是 SynchronousQueue 类型的,而 SynchronousQueue 是长度为 0 的阻塞队列,所以 workQueue 不存储任何等待执行的任务
如果线程池内存在空闲线程,那么新提交的任务会被空闲线程执行,如果线程池内没有空闲线程,那么线程池会创建新的线程来执行新提交的任务
除此之外,线程池大小为 Integer.MAX_VALUE,因此线程池中创建的线程个数可以非常多
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor( 0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>() ); }
1.4、newScheduledThreadPool()
newScheduledThreadPool() 函数用来创建一个支持定时或周期性的执行任务的线程池,其部分代码实现如下所示
线程池的核心线程池大小为 corePoolSize,整个线程池的大小为 Integer.MAX_VALUE
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService { public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); } // 定时执行任务(只执行一次) public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { ... } // 周期性执行任务(固定频率) // period 指的是上一个任务执行开始与下一个任务执行开始之间的时间间隔 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { ... } // 周期性执行任务(固定间隔) // delay 指的是上一个任务执行结束与下一个任务开始之间的时间间隔 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { ... } // ... 省略其他方法 ... }
2、Runnable、Callable、Future
2.1、示例 1
前面讲到,当我们使用线程执行任务,我们需要将任务封装成 Runnable 对象,并将任务对应的业务逻辑放入 Runnable 的 run() 方法
因为 run() 方法没有返回值,所以如果我们需要在另一个线程中获取这个线程的执行结果,那么我们需要让两个线程共享同一个结果变量
示例代码如下所示,线程 sumThread 将计算结果通过共享变量 sumRes 传递给了线程 main
public class Demo { private static final List<Integer> data = Arrays.asList(3, 4, 6, 2, 1); private static int sumRes = 0; public static void main(String[] args) throws InterruptedException { Thread sumThread = new Thread(new Runnable() { @Override public void run() { for (Integer d : data) sumRes += d; } }); sumThread.start(); sumThread.join(); System.out.println(sumRes); } }
2.2、示例 2
除了共享变量之外,线程执行框架提供了另外一种返回线程执行结果的方法,示例代码如下所示
- Callable 替代 Runnable 来表示待执行的任务
- 带返回值的 call() 函数替代不带返回值的 run() 函数封装业务逻辑
- submit() 函数替代 execute() 函数提交任务
Future 上的 get() 函数是阻塞函数,只有当 call() 函数执行结束返回之后,get() 函数才从阻塞中唤醒
public class Demo { private static final List<Integer> data = Arrays.asList(3, 4, 6, 2, 1); public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Integer> future = executor.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sumRes = 0; for (Integer d : data) sumRes += d; return sumRes; } }); Integer sumRes = future.get(); // 阻塞函数 System.out.println(sumRes); } }
int[] arr = {1, 2, 3, 4, 5}; Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i : arr) sum += i; return sum; } }; FutureTask<Integer> task = new FutureTask<>(callable); new Thread(task).start(); Thread.sleep(100); if (task.isDone()) System.out.println("sum(arr[]) = " + task.get());
3、课后思考题
基于 Executor 接口,实现一个支持串行执行任务的 SerialExecutor 执行器
public class SerialExecutor implements Executor { private Executor executor = new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() ); @Override public void execute(@NotNull Runnable command) { executor.execute(command); } }
本文来自博客园,作者:lidongdongdong~,转载请注明原文链接:https://www.cnblogs.com/lidong422339/p/17495224.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步