概述
引入
我们之前介绍了
-
Runnable
,Callable
类,用来将我们的逻辑代码进行封装 -
Future
类,在上面的基础上增加了任务进度的控制及检测 -
FutureTask
类,用来兼容我们封装的Callable/Runnable
接口差异
我们之前讲的都是对任务的封装和执行效果的检测、控制,但是还有重要的一环没有做——创建子线程执行任务。
我们之前都是在主线程中自行创建线程,执行子任务。这种方法简单粗暴,但是有以下问题:
- 重复的、不涉及业务逻辑的代码不便于编程
- 频繁创建线程,在性能上有很大的开销
- 在极端调用情况下,无法合理利用机器资源【比如同时创建200个线程执行任务,电脑卡死,不一定比创建20个线程复用执行快】
我们今天了解的就是如何管理子线程的创建的接口Executor
。此接口最开始设计的目的是将任务提交与任务执行机制分离,从而更方便、科学的管理子任务的执行。
摘要
本文介绍了Executor
接口的设计理念,并自行设计了一个Executor
的实现类以减少大量创建子任务的编码、执行成本。
类介绍
类定位
设计此类用来将任务提交和任务执行机制分离,即将子线程的来路和子线程的管理从程序员手中取出来,专门实现一套完善、复杂的程序猿好用,资源利用效率还高。
注意
源码解读
/**
* 稍后执行提交的任务,该任务可能在以下几种线程中执行:
* 1. 新创建的线程
* 2. 线程池中的线程
* 3. 调用此方法的主线程
*
* @param command 任务逻辑代码封装成的接口实现类
* @throws RejectedExecutionException 如果 Executor 拒绝执行此任务抛出的异常
* @throws NullPointerException 如果入参为空抛出的异常
*/
void execute(Runnable command);
使用示例
示例
package com.gateway.concurrent.pool;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
/**
* @author lipengcheng3 Created date 2019-02-22 18:18
*/
public class MyThreadExecutor implements Executor {
private final Thread[] threads = new Thread[5];
private final BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>();
public MyThreadExecutor() {
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(new MyRunnable());
threads[i].setDaemon(true);
threads[i].start();
}
}
@Override
public void execute(Runnable command) {
try {
tasks.put(command);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class MyRunnable implements Runnable {
private volatile boolean flag = true;
@Override
public void run() {
while (flag) {
try {
Runnable runnable = tasks.take();
runnable.run();
} catch (Exception e) {
e.printStackTrace();
flag = false;
}
}
}
}
public static void main(String args[]) throws IOException, InterruptedException {
MyThreadExecutor myThreadExecutor = new MyThreadExecutor();
for (int i = 0; i < 40; i++) {
myThreadExecutor.execute(new TestCalss("任务" + i));
}
Thread.sleep(1000);
}
static class TestCalss implements Runnable {
private String name;
public TestCalss(String name) {
this.name = name;
}
@Override
public void run() {
Long sleepTime = (long) (Math.random() * 1000);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 完成,用时 " + sleepTime + " 毫秒");
}
}
}
核心逻辑介绍
思路很简单我们分三个块介绍:
-
MyThreadExecutor
的运行机制
我们使用一个固定长度为5的数组,创建了5个线程,运行我们定义好的
MyRunnable
的逻辑:即不停的从一个阻塞队列中取任务执行。向外暴露的
execute
方法,用于将任务不停的放入任务的阻塞队列中。
-
main
的运行机制
创建
MyThreadExecutor
实例后不停的往里放任务,放完后等待1000ms后退出程序。
使用思路
其实Executor
的实现思路很简单,我们要着重处理好以下几个问题:
任务的添加和获取的线程安全性
一个
Thread
在创建时。他执行的内容就已经确定了,我们应如何将Runnable
任务传给已经创建好的Thread
并执行以实现线程复用的问题-
如何处理好线程的回收问题
这里我就遇到了这个问题,虽然我们在代码中也没有做回收操作。一开始在运行程序时发现执行结束后程序不会退出。想了一下下秒懂,是应为那5个子线程还在跑。于是把那几个线程从用户线程切换为守护线程,这样在主线程也是唯一的用户线程等待1000ms退出后子线程随着jvm的停止自动被回收。
问题
在这里好好想想,怎么搞能写出伸缩性更好、更灵活易用的Executor
工具。
其实我上面写的实现已经有点像线程池了,只不过我都是写死并封装的,对用户透明。