详解线程池

    详解线程池

    引言:在最早的时候有写过关于线程池的文章,针对四种不同的线程池,讲解了他们的特性,本文将更加深入,从源码来解释四种不同的线程池框架。

    一、线程池的作用以及好处

    在JDK1.5以前,线程池的使用是十分简陋的,直到JDK1.5,在并发包下,才出现了现如今的线程池。

    线程池的作用是控制线程的数量,因为每一个线程都会占用系统的内存,以及在运行时,会争夺CPU的使用权,线程多了,系统很容易挂掉,线程少了,系统的资源就没办法得到完全的利用,这时,能够根据系统的状况定制线程的数量就显得十分的有效。控制了线程的数量,如果有新的任务提交,线程都在忙碌,线程池则会将任务加入到一个队列中进行等待,这时线程池最初的设计。

    线程池的好处就显而易见了,线程池可以避免频繁地新建和销毁线程,让线程重复利用,执行多个任务,同时定制线程数量。

    二、简述四种线程池的特性

    

    线程池都是实现了ExecutorService接口的,通过Executors类中的静态工厂方法来构建,JDK提供有如下的四种静态工厂方法,配置了四种类型的线程池(推荐使用):

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

    第一种线程池fixedThreadPool,提供了一个定长的线程池,可以加整形参数,当线程池中加入任务后,若有空闲的线程则用该线程跑任务,否则进入等待队列。

    第二种线程是cachedThreadPool,缓冲线程池,不定长,一旦有任务进来不存在可利用的空闲线程,便会为任务新建线程,空闲线程超过60s会被回收。

    第三种线程池singleThreadExecutor,单线程池,只有一个线程,如果线程因为异常结束,会有新的线程来替代,仅用这一个线程来处理任务队列。

    第四种线程是scheduledThreadPool,同样也是定长的线程池,但是可以执行定时的任务,以及周期性的任务。

    前三种线程池方法的使用都比较雷同,都是通过实例化线程池的execute(Thread thread)加入线程类,最后调用shutdown方法平滑关闭线程池(等线程池中线程的任务完全结束,才会关闭)。第四种线程池代码如下:

public class TestScheduledThreadPoolExecutor {

    public static void main(String[] args) {

        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常

                      @Override

                      publicvoid run() {

                           //throw new RuntimeException();

                           System.out.println("================");

                      }

                  }, 1000, 5000, TimeUnit.MILLISECONDS);

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的

                      @Override

                      publicvoid run() {

                           System.out.println(System.nanoTime());

                      }

                  }, 1000, 2000, TimeUnit.MILLISECONDS);

    }

}

    三、解释线程池的源码

    线程池的构造,点击进入工厂类方法,可以发现Executor类底层实现了一个方法ThreadPoolExecutor。该方法的完整签名为:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

    一共有着以上参数,第一个corePoolSize,顾名思义,为核心线程数,默认情况下会一直存在,即使空闲,不受keepAliveTime的约束;

    第二个则为最大的线程数,当核心线程都在忙碌,就会创建多余的线程,但是最大不能超过线程池最大数量;第三个为线程所存活的时间keepAliveTime;

    第四个为时间单位;

    第五个为一个阻塞队列的接口,用于存放加入的任务,workQueue;

    后两个为执行线程创建所调用的工厂,和当超出线程池数量和任务队列容量阻塞时执行的方法,称之为拒绝策略。

    (当使用有界队列时,若是新进了任务,但是核心线程全都在运作,则加入任务队列中,若任务队列也满了,则在没有超出线程池容量的前提下新建线程,若是超过最大线程数,执行该方法)

    (无界队列就不会那么尴尬,理论上他是永远不会执行handler方法的,因为当新进了一个任务,若核心线程在忙,就会进入队列中等待,不会再去新建线程,所以线程池线程最大数没有什么意义了此时)

    讲究的就一个策略:先核心,再队列,再新建,新建到最大就炸给你看。

    拓展:JDK提供了四种拒绝策略,若是想要自定义拒绝策略,必须实现一个reject**的接口,然后重新其中的一个方法,该方法传入了线程池对象和当前任务对象。

    让我们来看看不同线程池的工厂类源码:

    1、fixedThreadPool:

return new ThreadPoolExecutor(nThreads, nThreads,   
                                           0L, TimeUnit.MILLISECONDS,   
                                           new LinkedBlockingQueue<Runnable>()); 

    可以看出核心线程数和最大线程数是一样的,存活时间为0无效,使用了LinkedBlockingQueue无界阻塞队列来实现。

    2、singleThreadExecutor:

1.     public static ExecutorService newSingleThreadExecutor() {   

2.             return new FinalizableDelegatedExecutorService   

3.                 (new ThreadPoolExecutor(1, 1,   

4.                                         0L, TimeUnit.MILLISECONDS,   

5.                                         new LinkedBlockingQueue<Runnable>()));   

6.         }

    单线程线程池,核心线程数和最大线程池数量为1,存活时间也为0,同样使用了无界队列。

    3、cachedThreadPool:

1.     public static ExecutorService newCachedThreadPool() {   

2.             return new ThreadPoolExecutor(0, Integer.MAX_VALUE,   

3.                                           60L, TimeUnit.SECONDS,   

4.                                           new SynchronousQueue<Runnable>());   

    }

    缓存线程池的实现就十分有意思了,他没有核心线程数量,同时线程池的上限也可视为无,非核心线程存活时间为60S,使用了一个无缓存队列来实现阻塞队列,意味着不缓存任何任务,所有任务即来即用。(重点是没有核心线程)

    4、scheduledThreadPool:

super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    定时线程池定义了核心线程的数量,没有对线程池的数量设置上限,同时也没有让非核心线程有存活时间,最有意思的是他使用了一个定时队列来实现任务队列,完成定时的作用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值