5-Executor

2020-02-11  本文已影响0人  鹏程1995

概述

引入

我们之前介绍了

我们之前讲的都是对任务的封装和执行效果的检测、控制,但是还有重要的一环没有做——创建子线程执行任务。

我们之前都是在主线程中自行创建线程,执行子任务。这种方法简单粗暴,但是有以下问题:

  1. 重复的、不涉及业务逻辑的代码不便于编程
  2. 频繁创建线程,在性能上有很大的开销
  3. 在极端调用情况下,无法合理利用机器资源【比如同时创建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 + " 毫秒");
        }
    }

}

核心逻辑介绍

思路很简单我们分三个块介绍:

  1. MyThreadExecutor的运行机制

我们使用一个固定长度为5的数组,创建了5个线程,运行我们定义好的MyRunnable的逻辑:即不停的从一个阻塞队列中取任务执行。

向外暴露的execute方法,用于将任务不停的放入任务的阻塞队列中。

  1. main的运行机制

创建MyThreadExecutor实例后不停的往里放任务,放完后等待1000ms后退出程序。

使用思路

其实Executor的实现思路很简单,我们要着重处理好以下几个问题:

  1. 任务的添加和获取的线程安全性

  2. 一个Thread在创建时。他执行的内容就已经确定了,我们应如何将Runnable任务传给已经创建好的Thread并执行以实现线程复用的问题

  3. 如何处理好线程的回收问题

    这里我就遇到了这个问题,虽然我们在代码中也没有做回收操作。一开始在运行程序时发现执行结束后程序不会退出。想了一下下秒懂,是应为那5个子线程还在跑。于是把那几个线程从用户线程切换为守护线程,这样在主线程也是唯一的用户线程等待1000ms退出后子线程随着jvm的停止自动被回收。

问题

在这里好好想想,怎么搞能写出伸缩性更好、更灵活易用的Executor工具。

其实我上面写的实现已经有点像线程池了,只不过我都是写死并封装的,对用户透明。

扩展

参考文献

上一篇下一篇

猜你喜欢

热点阅读