只是不愿随波逐流 ...|

lidongdongdong~

园龄:2年7个月粉丝:14关注:8

54、线程执行框架

内容来自王争 Java 编程之美

本节我们来讲线程执行框架,线程执行框架提供了一系列类,封装了线程:创建、关闭、执行、管理等跟业务逻辑无关的代码逻辑

  • 实现了业务逻辑与非业务逻辑的解耦
  • 方便代码复用,开发者不再需要编写创建线程、启动线程等代码

上一节中讲到的 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 工具类

image

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);
}
}
posted @   lidongdongdong~  阅读(49)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开