zoukankan      html  css  js  c++  java
  • java自定义线程池

    如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。首先我们从最核心的ThreadPoolExecutor类中的方法讲起

        java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

    ThreadPoolExecutor类中提供了四个构造方法:

    public class ThreadPoolExecutor extends AbstractExecutorService {

        .....

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

                BlockingQueue<Runnable> workQueue);

     

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

                BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

     

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

                BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

     

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

        ...

    }

     从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

    下面解释下一下构造器中各个参数的含义:

    corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

    maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

    keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0

    unit:参数keepAliveTime的时间单位

    workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

    ArrayBlockingQueue;

    LinkedBlockingQueue;

    SynchronousQueue;

    threadFactory:线程工厂,主要用来创建线程;

    handler:表示当拒绝处理任务时的策略,默认有以下四种取值:

    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

    线程池执行的流程:

    当任务提交给ThreadPoolExecutor 线程池中,先检查核心线程数是否已经全部使用,如果没有交由核心线程去执行任务,如果核心线程数已经全部占用,则将任务添加到队列里面,如果队列已经占满,比较当前线程池的中线程的数量是不是与超过maximumPoolSize如果没有查过则创建线程去执行,也就是说线程池最多可以接受多少任务呢?就是maximumPoolSize+队列的大小。当线程池中的线程的数量大于corePoolSize数量有空闲线程则执行回收,回收时间是keepAliveTime单位是unit,都是初始化的时候设置的。

    下面通过代码来说明:

    定义一个实现了Runnable接口的类,当作任务类;

    public class MyTask implements Runnable {

        private int taskId;

        private String taskName;

        public int getTaskId() {

            return taskId;

        }

        public void setTaskId(int taskId) {

            this.taskId = taskId;

        }

        public String getTaskName() {

            return taskName;

        }

        public void setTaskName(String taskName) {

            this.taskName = taskName;

        }

        public MyTask(int taskId, String taskName) {

            this.taskId = taskId;

            this.taskName = taskName;

        }

        @Override

        public void run() {

            System.out.println("taskId:" + taskId + ",taskName:" + taskName);

            try {

                Thread.sleep(10000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

    定义如下的线程池:

     ThreadPoolExecutor pool = new ThreadPoolExecutor(// 自定义一个线程池

                    1, // coreSize

                    2, // maxSize

                    60, // 60s

                    TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列,容量是3

                    , Executors.defaultThreadFactory()

                    , new ThreadPoolExecutor.AbortPolicy()

            );

    该线程池最多可以放2+3个任务,现在我们放6个任务进去,看看执行的效果:

      pool.execute(new MyTask(1, "任务1"));

            System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());

            pool.execute(new MyTask(2, "任务2"));

            System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());

            pool.execute(new MyTask(3, "任务3"));

            System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());

            pool.execute(new MyTask(4, "任务4"));

            System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());

            pool.execute(new MyTask(5, "任务5"));

            System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());

            pool.execute(new MyTask(6, "任务6"));

            System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());

            pool.shutdown();

    我们的执行结果:

     

    我们可以看到,首先抛出了异常,大致意思是拒绝了一个线程加入到线程池,因为我线程池最大允许5个线程的加入,当线程池满了执行的拒绝策略是DiscardPolicy直接拒绝线程的加入,并抛出异常。

    接下来,我们看每次添加一个线程打印的活跃的线程数等相关消息。

    当任务1加入到线程池中:

    活跃的线程数:1,核心线程数:1,线程池大小:1,队列的大小0,也就是说,任务1加入核心未被占满,开启一个核心线程去执行。此时线程的大小也为1.

    当任务2加入到线程池中时:

    活跃的线程数:1,核心线程数:1,线程池大小:1,队列的大小1 。也就是说,此时核心已经占满了,队列没有满,则往队列里面增加任务。此时线程的大小仍然也为1.因为就一个核心线程在执行任务。

    当任务3加入到线程池中时:同任务2.

    当任务4加入到线程池中时:同任务2.此时队列已经满。

    当任务5加入到线程池中时:

    活跃的线程数:2,核心线程数:1,线程池大小:2,队列的大小3,活跃的线程变为2,也就是maximumPoolSize数量,因为任务4加入到线程池时,线程池的队列已经满了,此时会检查活跃的线程是不是大于maximumPoolSize如果不大于则创建线程去执行任务,到底执行新加入还是队列里面最老加入的。此时通过下面的执行结果来判断。

    我们看到任务1和任务5最先执行,任务1不用讲自然会在最先执行的里面,任务5在最先执行的任务里面,说明,当线程队列满了,如果开起了新线程,则会去执行新加入的任务,不是从队列里面去老的任务。从后面执行来看,当之前的任务直线完成了,线程池会从队列里面获取任务去执行。这就是一个线程池的大致执行流程。

    当然个人觉得那个原生的拒绝策略都不太实用,比如互联网任务,我们定义一个线程池,当线程池满了,我们要合理的处理后续的任务,比如记录下来下次再去执行,或者告知责任人那些任务没有处理等等,个人任务这个应该自己定义,当线程满了,我们可以自由控制。下面定义一个拒绝策略。

    public class MyRejected implements RejectedExecutionHandler {

        @Override

        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

            MyTask task = (MyTask) r;

            System.out.println("报警信息:"+task.getTaskName()+" 被线程池拒绝,没有被执行");

            //可以往消息队列中间件里面放 可以发Email等等

        }

    }

    如上,我们实现RejectedExecutionHandler 接口。就可以自定义一个拒绝策略很简单。

    我们需要线程池的定义,使用自己的拒绝策略。

        ThreadPoolExecutor pool = new ThreadPoolExecutor(// 自定义一个线程池

                    1, // coreSize

                    2, // maxSize

                    60, // 60s

                    TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列,容量是3

                    , Executors.defaultThreadFactory()

                    , new MyRejected()

            );

    其他的代码不用修改,执行结果如下:

     

    现在就执行了自己自定义拒绝策略。

    以上只是讲解的自定义的线程池,当然java本身已经内置了一些线程,比如:

    newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

    newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

    这里就不详细讲解了,内部实现都是ThreadPoolExecutor ,个人倾向自定义的线程池,这样比较灵活。

  • 相关阅读:
    CodeIgniter(3.1.4)框架中成功/错误跳转
    CodeIgniter(3.1.4)框架-url重写,去除index.php
    CodeIgniter(3.1.4)框架中-使用多个公共控制器
    CodeIgniter(3.1.4)框架使用静态文件(js,css)
    PHP无限极分类
    PHP无限极分类
    spring中定时任务quartz2.2.3
    eclipse中git更新操作
    java中日期的换算处理
    使用spring-rabbit测试RabbitMQ消息确认(发送确认,接收确认)
  • 原文地址:https://www.cnblogs.com/nele/p/6502750.html
Copyright © 2011-2022 走看看