创建自己的多线程池

创建自己的多线程池类

本文通过ThreadGroup创建线程池类。

  • 线程池介绍
  • 自定义线程
  • JDK自带线程池分析

线程池介绍

线程池就是预先创建一些工作线程,它们不断从工作队列中取出任务,然后完成任务。当工作线程执行完一个任务后,就会继续执行工作队列中的下一个任务。

线程池优点

  • 减少了线程创建和销毁的次数,每个工作线程都可以一直被重用,能执行多个任务。
  • 可以根据系统的承载能力,自由调整线程池中线程的数目。防止因为消耗过量资源而导致系统崩溃。

    自定义线程

    代码如下:

    public class ThreadPool extends ThreadGroup{
    private boolean isClosed = false;
    private LinkedList<Runnable> workQueue;
    private static int threadPoolID;
    private int threadID;
    
    public ThreadPool(int poolSize){
        super("ThreadPool-"+(threadPoolID++));
        setDaemon(true);
        workQueue = new LinkedList<>();//创建工作队列
        for(int i=0;i<poolSize;i++){
            new WorkThread().start();//创建并启动工作线程
        }
    }
    
    /**
     * 获取task
     * @return
     * @throws InterruptedException
     */
    protected synchronized Runnable getTask() throws InterruptedException {
        while (workQueue.size()==0){
            if(isClosed){
                return null;
            }
            wait(); //如果任务队列中没有任务,就等待任务
        }
       return workQueue.removeFirst();
    }
    
    /**
     * 关闭线程池
     */
    public synchronized void close(){
        if(!isClosed){
            isClosed = true;
            workQueue.clear();//清空任务队列
            interrupt();//中断所有的工作线程,该方法继承ThreadGroup类
        }
    }
    public void join(){
        synchronized (this){
            isClosed = true;
            notifyAll();//唤醒还在getTask()方法中等待任务的工作线程
        }
        Thread[] threads = new Thread[activeCount()];
        int count = enumerate(threads);//后的线程组中当前所有活着的工作线程
        for(int i=0;i<count;i++){//等待所有工作线程结束
            try {
                threads[i].join(); //等待工作线程运行结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 执行任务
     * @param task
     */
    public synchronized void execute(Runnable task){
        if(isClosed){
            throw new IllegalStateException();
        }
        if(task!=null){
            workQueue.add(task);
            notify();//唤醒正在getTask()方法中等待任务的工作线程
        }
    }
    
    
    
    private class WorkThread extends Thread{
    
        public WorkThread(){
            super(ThreadPool.this,"WorkThread-"+(threadID++));
        }
    
        @Override
        public void run() {
            while(!isInterrupted()){//判断线程是否被中断
                Runnable task = null;
                try {
                    task = getTask();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(task==null){
                    return;
                }
                try {
                    task.run();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    }

    ThreadPool的测试类:

public class ThreadPoolTest {
    public static void main(String[] args) {
        int numTask = 10;
        int poolSize = 4;

        ThreadPool threadPool = new ThreadPool(poolSize);

        for(int i=0;i<numTask;i++){
          threadPool.execute(createTask(i));
        }
        threadPool.close();
    }

    private static Runnable createTask(final int i) {
        return new Runnable() {
            @Override
            public void run() {
                System.out.println("Task "+ i +":start");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task "+ i +":end");
            }
        };
    }
}

代码分析

  • 在ThreadPool类中定义了一个LinkedList类型的workQueue成员变量,它表示工作队列,用来存放线程池要执行的任务,每个任务都是Runnable实例。ThreadPool的测试方法只需调用execute()方法,就能向ThreadPool提交任务。

  • 在ThreadPool类的构造方法中,会创建并启动若干工作线程,工作线程的数目由参数poolSize决定。WorkThread表示工作线程,它是ThreadPool类的内部类。工作线程从工作队列中获取一个任务,接着执行该任务,然后再从工作队列中取出下一个任务并执行它,如此反复。

  • 在execute()方法中:先判断该线程池是否已经关闭,如果关闭抛出IllegalStateException异常。如果没有关闭的话,将任务加到workQueue中,然后在唤醒getTask()方法中获取任务的线程。

  • getTask()方法实现:如果队列为空且线程池已关闭,则返回null。如果队列为空线程池没有关闭,则在此等待。如果队列中有任务则取出第一个将其返回。

  • join()和close()方法都可用于关闭线程池。join()方法确保在关闭线程之前,工作线程把队列中的所有任务都执行完。而close()方法则情况队列,并中断所有工作线程。

测试类执行结果:
这里写图片描述

JDK自带线程池分析

测试JDK自带线程

代码如下:

public class ThreadPoolTest2 {
    public static void main(String[] args) {
        int poolSize = 4;
        ExecutorService executorService =  Executors.newFixedThreadPool(poolSize);
        for(int i=0;i<10;i++) {
            executorService.execute(createTask(i));
        }
        //executorService.shutdownNow(); //与自定义的线程池的close()方法类似
        executorService.shutdown(); //与自定义的线程池的join()方法类似
    }

    private static Runnable createTask(final int taskId) {
        return  new Runnable() {

            @Override
            public void run() {
                System.out.println("Task " + taskId + ":start");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + ":end");
            }
        };
    }
}

执行结果如下:
这里写图片描述

在上面代码中通过ExecutorService executorService = Executors.newFixedThreadPool(poolSize); 创建线程池,该方法时创建一个固定数目的线程池。
进入newFixedThreadPool 方法内,内容如下:
这里写图片描述
这个方法这是个幌子,在进入ThreadPoolExecutor类中 的另一个构造方法点击 this 进入 查看各个参数表示什么意思:

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

参数 corePoolSize :核心线程池线程数目。

参数 maximumPoolSize :最大线程池线程数。

参数 keepAliveTime :空闲时间,如果线程池中的线程数大于corePoolSize的话,则这些多出来的线程在空闲时间超过keepAliveTime时将会销毁。

参数 unit :keepAliveTime参数的单位。

参数 workQueue :工作队列。工作队列支持三种策略:

  • 直接提交。工作队列的默认选项是 SynchronousQueue 一种阻塞队列。其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。
  • 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙于新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;
  • 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致降低吞吐量。

参数 threadFactory :线程工厂。使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。

参数 handler :被拒绝的任务处理的handler。当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被 拒绝。在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时异常 RejectedExecutionException

类图

ExecutorExecutorServiceExecutors 三个类直接的关系:
这里写图片描述

总结:

其实我们自己定义的线程池也是借鉴JDK自带的线程池的。在创建一个线程池的时候,都有这几个必要元素:线程池大小线程存活时间线程工厂工作队列任务拒绝处理方法
在使用JDK的线程池中,我们只用到两个类,一个是ExecutorsExecutorService 这个是接口。其实还有一个接口很重要,因为它是execute()方法的申明类,也是ExecutorService 的父接口,就是 Executor 。这三个类各司其职:

类名作用
Executor线程池类,它的execute()方法用来执行Runnable类型的任务。
ExecutorService是Executor的子接口,申明了一些管理线程池的方法。比如shutdow()
ExecutorsExecutors包含一些静态方法,它主要负责创建各种类型的ExecutorService实例。

欢迎关注微信公众号 在路上的coder 每天分享优秀的Java技术文章!
扫描二维码关注:这里写图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值