关闭

创建自己的多线程池

标签: 多线程线程池
544人阅读 评论(0) 收藏 举报
分类:

创建自己的多线程池类

本文通过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()
Executors Executors包含一些静态方法,它主要负责创建各种类型的ExecutorService实例。

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

1
0
查看评论

自己手动写个线程池

前言:   要自定义一个线程池,首先要有存固定的线程数目的阻塞队列,还要有个存放任务的阻塞队列。   如果任务放入时有线程为空闲直接交付于它执行否则就放入任务队列中   在内部有个线程会不断的扫描任务队列如果既有空闲任务又有空闲线程就执行。   实现...
  • yinbucheng
  • yinbucheng
  • 2016-07-26 15:03
  • 559

自己动手实现简单的线程池

为了节省系统在多线程并发情况下不断的创建新和销毁线程所带来的性能浪费,就需要引入线程池。 线程池的基本功能就是线程复用。每当系统提交一个任务时,会尝试从线程池内取出空闲线程来执行它。如果没有空闲线程,这时候再创建新的线程。任务执行完毕,线程也不会立即销毁,而是加入到线程池中以便下次复...
  • zq602316498
  • zq602316498
  • 2014-12-09 09:12
  • 2077

c#自己实现线程池功能(一)

线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源,所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源...
  • zhujunxxxxx
  • zhujunxxxxx
  • 2014-10-20 19:06
  • 10007

自己设计线程池

一,线程池的基本要素 线程池一般需要一个线程管理类: ThreadPoolManager,其作用有:   1)提供创建一定数量的线程的方法。主线程调用该方法,从而创建线程。创建的线程执行自己的例程,线程的例程阻塞在任务抓取上。   2)提供对任务队列的操作的方法。主线程调用初...
  • seulzz
  • seulzz
  • 2017-08-20 17:24
  • 181

java线程池--自己编写的线程池,非Eclipse自带

package threadPool; import java.util.Vector; // TODO: Auto-generated Javadoc /**  * 线程池应该是这样一个概念: 维护一个线程队列,该队列始终运行 维护一个工作队列, 当发现有空余线程...
  • u011715213
  • u011715213
  • 2014-10-21 10:39
  • 976

java线程池学习(二) —— 实现一个简单的线程池

在上一篇文章中《java线程池学习(一) —— BlockingQueue》,我们简单探讨了一个BlockingQueue的概念。 那么在这边文章,我们要利用BlockingQueue来自己实现一个简单的线程池,在以后的章节中,我们再学习一下怎么去使用java为我们封装好的线程池。 首先我们关注一个...
  • Great_Smile
  • Great_Smile
  • 2015-10-03 17:01
  • 2796

创建线程池的几种方式

我们可以通过Executors的静态方法来创建线程池。 newFixedThreadPool(int nThreads) newCachedThreadPool() newSingleThreadExecutor() newScheduledThreadPool(int corePoolSi...
  • cyantide
  • cyantide
  • 2016-03-13 21:07
  • 6088

线程池的几种常见的创建的方式

每次启动一个线程都要创建一个新的浪费资源的,还有时候线程过多的时候回造成服务器崩溃,所以有了线程池的诞生,线程池是用来管理线程的,下面是常用的几种创建线程的方式: 一:创建大小不固定的线程池//这是一个线程类 public class ThreadChi implements Runnable{ ...
  • HANLIPENGHANLIPENG
  • HANLIPENGHANLIPENG
  • 2016-08-03 22:56
  • 7503

利用Java编写自己的线程池

前言:         自JDK1.5问世以来JDK的开发者在线程处理方案中提供了很多多线程方面的工具包在java.util.current包下,尤其显著的就是线程池的出现,也算是JDK的一个 里程碑版本;        ...
  • y_h_u_abc
  • y_h_u_abc
  • 2017-03-15 17:00
  • 261

Java ExecutorService四种线程池的例子与说明

1、new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).sta...
  • NK_TF
  • NK_TF
  • 2016-07-19 20:40
  • 27511
    欢迎关注个人微信号
      欢迎关注微信账号:在路上的coder .每天一篇java相关或互联网相关的文章




    个人资料
    • 访问:825323次
    • 积分:2953
    • 等级:
    • 排名:第14104名
    • 原创:87篇
    • 转载:4篇
    • 译文:0篇
    • 评论:26条
    资源分享地址
    个人博客地址
    博客专栏
    最新评论