课程咨询: 400-996-5531 / 投诉建议: 400-111-8989
认真做教育 专心促就业
做这个多线程异步任务,主要是因为我们有很多永动的异步任务,什么是永动呢?就是任务跑起来后,需要一直跑下去。
比如消息 Push 任务,因为一直有消息过来,所以需要一直去消费 DB 中的未推送消息,就需要整一个 Push 的永动异步任务。
我们的需求其实不难,简单总结一下:
完成上面的需求,需要注意几个点:
对于子任务,需要支持并发,如果每个并发都开一个线程,用完就关闭,对资源消耗太大,所以引入线程池:
public class TaskProcessUtil {// 每个任务,都有自己单独的线程池private static Map<String, ExecutorService> executors =new ConcurrentHashMap<>();// 初始化一个线程池private static ExecutorService init(String poolName, int poolSize) {return new ThreadPoolExecutor(poolSize, poolSize, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("Pool-" + poolName).setDaemon(false).build(),new ThreadPoolExecutor.CallerRunsPolicy());}// 获取线程池public static ExecutorService getOrInitExecutors(String poolName, int poolSize) {ExecutorService executorService = executors.get(poolName);if (null == executorService) {synchronized (TaskProcessUtil.class) {executorService = executors.get(poolName);if (null == executorService) {executorService = init(poolName, poolSize);executors.put(poolName, executorService);}}}return executorService;}// 回收线程资源public static void releaseExecutors(String poolName) {ExecutorService executorService = executors.remove(poolName);if (executorService != null) {executorService.shutdown();}}}
这是一个线程池的工具类。这里初始化线程池和回收线程资源很简单,我们主要讨论获取线程池。
获取线程池可能会存在并发情况,所以需要加一个 synchronized 锁。锁住后,需要对 executorService 进行二次判空校验。
为了更好讲解单个任务的实现方式,我们的任务主要就是把 Cat 的数据打印出来,Cat 定义如下:
public class Cat {private String catName;public Cat setCatName(String name) {this.catName = name;return this;}}
单个任务主要包括以下功能:
直接看代码:
public class ChildTask {private final int POOL_SIZE = 3; // 线程池大小private final int SPLIT_SIZE = 4; // 数据拆分大小private String taskName;// 接收jvm关闭信号,实现优雅停机protected volatile boolean terminal = false;public ChildTask(String taskName) {this.taskName = taskName;}// 程序执行入口public void doExecute() {int i = 0;while (true) {System.out.println(taskName + ":Cycle-" + i + "-Begin");// 获取数据List<Cat> datas = queryData();// 处理数据taskExecute(datas);System.out.println(taskName + ":Cycle-" + i + "-End");if (terminal) {// 只有应用关闭,才会走到这里,用于实现优雅的下线break;}i++;}// 回收线程池资源TaskProcessUtil.releaseExecutors(taskName);}// 优雅停机public void terminal() {// 关机terminal = true;System.out.println(taskName + " shut down");}// 处理数据private void doProcessData(List<Cat> datas, CountDownLatch latch) {try {for (Cat cat : datas) {System.out.println(taskName + ":" + cat.toString()+ ",ThreadName:" + Thread.currentThread().getName());Thread.sleep(1000L);}} catch (Exception e) {System.out.println(e.getStackTrace());} finally {if (latch != null) {latch.countDown();}}}// 处理单个任务数据private void taskExecute(List<Cat> sourceDatas) {if (CollectionUtils.isEmpty(sourceDatas)) {return;}// 将数据拆成4份List<List<Cat>> splitDatas = Lists.partition(sourceDatas, SPLIT_SIZE);final CountDownLatch latch = new CountDownLatch(splitDatas.size());// 并发处理拆分的数据,共用一个线程池for (final List<Cat> datas : splitDatas) {ExecutorService executorService =TaskProcessUtil.getOrInitExecutors(taskName, POOL_SIZE);executorService.submit(new Runnable() {@Overridepublic void run() {doProcessData(datas, latch);}});}try {latch.await();} catch (Exception e) {System.out.println(e.getStackTrace());}}// 获取永动任务数据private List<Cat> queryData() {List<Cat> datas = new ArrayList<>();for (int i = 0; i < 5; i++) {datas.add(new Cat().setCatName("罗小黑" + i));}return datas;}}
简单解释一下:
直接上代码:
public class LoopTask {private List<ChildTask> childTasks;public void initLoopTask() {childTasks = new ArrayList();childTasks.add(new ChildTask("childTask1"));childTasks.add(new ChildTask("childTask2"));for (final ChildTask childTask : childTasks) {new Thread(new Runnable() {public void run() {childTask.doExecute();}}).start();}}public void shutdownLoopTask() {if (!CollectionUtils.isEmpty(childTasks)) {for (ChildTask childTask : childTasks) {childTask.terminal();}}}public static void main(String args[]) throws Exception {LoopTask loopTask = new LoopTask();loopTask.initLoopTask();Thread.sleep(5000L);loopTask.shutdownLoopTask();}}
每个任务都开一个单独的 Thread。这里我初始化了 2 个永动任务,分别为 childTask1 和 childTask2,然后分别执行,后面 Sleep 了 5 秒后,再关闭任务,我们可以看看是否可以按照我们的预期优雅退出。
执行结果如下:
childTask1:Cycle-0-BeginchildTask2:Cycle-0-BeginchildTask1:Cat(catName=罗小黑0),ThreadName:Pool-childTask1childTask1:Cat(catName=罗小黑4),ThreadName:Pool-childTask1childTask2:Cat(catName=罗小黑4),ThreadName:Pool-childTask2childTask2:Cat(catName=罗小黑0),ThreadName:Pool-childTask2childTask1:Cat(catName=罗小黑1),ThreadName:Pool-childTask1childTask2:Cat(catName=罗小黑1),ThreadName:Pool-childTask2childTask2:Cat(catName=罗小黑2),ThreadName:Pool-childTask2childTask1:Cat(catName=罗小黑2),ThreadName:Pool-childTask1childTask2:Cat(catName=罗小黑3),ThreadName:Pool-childTask2childTask1:Cat(catName=罗小黑3),ThreadName:Pool-childTask1childTask2:Cycle-0-EndchildTask2:Cycle-1-BeginchildTask1:Cycle-0-EndchildTask1:Cycle-1-BeginchildTask2:Cat(catName=罗小黑0),ThreadName:Pool-childTask2childTask2:Cat(catName=罗小黑4),ThreadName:Pool-childTask2childTask1:Cat(catName=罗小黑4),ThreadName:Pool-childTask1childTask1:Cat(catName=罗小黑0),ThreadName:Pool-childTask1childTask1 shut downchildTask2 shut downchildTask2:Cat(catName=罗小黑1),ThreadName:Pool-childTask2childTask1:Cat(catName=罗小黑1),ThreadName:Pool-childTask1childTask1:Cat(catName=罗小黑2),ThreadName:Pool-childTask1childTask2:Cat(catName=罗小黑2),ThreadName:Pool-childTask2childTask1:Cat(catName=罗小黑3),ThreadName:Pool-childTask1childTask2:Cat(catName=罗小黑3),ThreadName:Pool-childTask2childTask1:Cycle-1-EndchildTask2:Cycle-1-End
输出数据:
我们分析一下执行结果: