首页 > 编程语言 > 详细

线程池总结

时间:2019-03-03 17:53:01      阅读:160      评论:0      收藏:0      [点我收藏+]

一、为什么使用线程池

  1)提高性能:系统启动一个新线程的成本是比较高的,而使用线程池避免了频繁地创建和销毁线程,可以很好地提高性能。线程池里的线程结束后并不会死亡,而是回到线程池中称为空闲线程,等待使用;

  2)控制线程数量:使用线程池还可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系统中并发线程数量。

二、一个使用线程池的简单案例

  步骤:
  1)调用Executors类的静态方法创建一个ExecutorService对象,该对象代表一个线程池;
  2)创建Runnable实现类或Callable实现类的实例,作为线程执行任务;
  3)调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例;
  4)当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池;

public class ThreadPoolTest{
    public static void main(String[] args){
        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(6);
        //2.创建要执行的线程任务
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        //3.提交线程任务
        pool.submit(mt1);
        pool.submit(mt2);
        //4.关闭线程池
        pool.shutdown();
    }
}

class MyThread implements Runnable{
    public void run(){
        for(int i = 0; i < 100; i++){
            System.out.println("MyThread--" + "i");
        }
    }
}

 

三、深入了解线程池的参数

  ThreadPoolExecutor是java线程池框架(Executor-> ExecutorService)的主要实现类,它的构造函数参数如下:

  public ThreadPoolExecutor( int corePoolSize,    //核心线程的数量
                int maximumPoolSize,    //最大线程数量
                                  long keepAliveTime,    //超出核心线程数量以外的线程空余存活时间
                                  TimeUnit unit,    //存活时间的单位
                                  BlockingQueue<Runnable> workQueue,    //保存待执行任务的队列
                                 ThreadFactory threadFactory,    //创建新线程使用的工厂
                                  RejectedExecutionHandler handler // 当任务无法执行时的处理器 )

  对线程池的参数的介绍:

  1)corePoolSize:核心线程池数量 。在线程数少于核心数量时,有新任务进来就新建一个线程,即使有的线程没事干;等超出核心数量后,就不会新建线程了,空闲的线程就得去任务队列里取任务执行了。
  2)maximumPoolSize:最大线程数量 ,包括核心线程池数量 + 核心以外的数量。
    如果任务队列满了,并且池中线程数小于最大线程数,会再创建新的线程执行任务。
  3)keepAliveTime:核心池以外的线程存活时间,即没有任务的外包的存活时间
    如果给线程池设置 allowCoreThreadTimeOut(true),则核心线程在空闲时头上也会响起死亡的倒计时
    如果任务是多而容易执行的,可以调大这个参数,那样线程就可以在存活的时间里有更大可能接受新任务
  4)workQueue:保存待执行任务的阻塞队列 。线程池中使用的队列是 BlockingQueue 接口,常用的实现有如下几种:

    a)ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序;

    b)LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素;

      - 吞吐量通常要高于 ArrayBlockingQueue

      - Executors.newFixedThreadPool() 使用了这个队列

    c)SynchronousQueue:不存储元素的阻塞队列;

      - 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

      - 吞吐量通常要高于 LinkedBlockingQueue

      - Executors.newCachedThreadPool使用了这个队列

    d)PriorityBlockingQueue:具有优先级的、无限阻塞队列。


  5)threadFactory:每个线程创建的地方。可以给线程起个好听的名字,设置个优先级等。
  6)handler:饱和策略,大家都很忙,咋办呢,有四种策略 。
    CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务
    AbortPolicy:直接抛出 RejectedExecutionException 异常
    DiscardPolicy:悄悄把任务放生,不做了
    DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用 execute() 试试看能行不

四、线程池的处理流程

  线程池具体的执行方法ThreadPoolExecutor.execute:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    //1.当前池中线程比核心数少,新建一个线程执行任务
    if (workerCountOf(c) < corePoolSize) {   
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2.核心池已满,但任务队列未满,添加到队列中
    if (isRunning(c) && workQueue.offer(command)) {   
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))    //如果这时被关闭了,拒绝任务
            reject(command);
        else if (workerCountOf(recheck) == 0)    //如果之前的线程已被销毁完,新建一个线程
            addWorker(null, false);
    }
    //3.核心池已满,队列已满,试着创建一个新线程
    else if (!addWorker(command, false))
        reject(command);    //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

  线程池的处理流程图:

技术分享图片

 

五、JDK 提供的线程池及使用场景

  1.java.util.concurrent.Executors是JDK自带的创建线程的工具类,其创建的线程池是ThreadPoolExecutor的实例。Executors创建线程池比较典型的4种方式:

  1) newFixedThreadPool:创建一个核心线程个数和最大线程个数都为nThreads的线程池,并且阻塞队列长度为Integer.MAX_VALUE,keeyAliveTime=0说明只要线程个数比核心线程个数多并且当前空闲则回收。可能引发的问题:阻塞队列太大会引起OOM;核心线程会常驻内存,可能会造成资源浪费。

  2)newSingleThreadExecutor:创建一个核心线程个数和最大线程个数都为1的线程池,并且阻塞队列长度为Integer.MAX_VALUE,keeyAliveTime=0说明只要线程个数比核心线程个数多并且当前空闲则回收。缺点:单线程,可能会堆积大量待执行的任务。

  3)newCachedThreadPool: 创建一个按需创建线程的线程池,最大线程数为Integer.MAX_VALUE,阻塞队列为最多只有一个任务的同步队列,keeyAliveTime=60只要线程60s内空闲则回收。问题点:最大线程数太大会引起OOM。

  4)newScheduledThreadPool:创建一个最小线程个数corePoolSize,最大为Integer.MAX_VALUE,阻塞队列为DelayedWorkQueue的线程池。

  

  2.如何选择使用哪个JDK 提供的线程池?

    - CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。
    - FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。
    - SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。
    - ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

  3.注意:Executors创建线程的方式虽然简单方便,但并不推荐使用此种方式。建议使用new ThreadPoolExecutor(...)的方式创建线程池。

 

六、两种提交任务的方法

  ExecutorService 提供了两种提交任务的方法:
    execute():提交不需要返回值的任务
    submit():提交需要返回值的任务

七、线程池使用注意事项

  1)建议使用new ThreadPoolExecutor(...)的方式创建线程池:

  线程池的创建不应使用 Executors 去创建,而应该通过 ThreadPoolExecutor 创建,这样可以让读者更加明确地知道线程池的参数设置、运行规则,规避资源耗尽的风险,这一点在也阿里巴巴JAVA开发手册中也有明确要求。

  2)合理设置线程数:

  线程池的工作线程数设置应根据实际情况配置,CPU密集型业务(搜索、排序等)CPU空闲时间较少,线程数不能设置太多。N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。

  3)设置能代表具体业务的线程名称:这样方便通过日志的线程名称识别所属业务。具体实现可以通过指定ThreadPoolExecutor的ThreadFactory参数。如使用spring提供的CustomizableThreadFactory。

 

参考文献:

  1)https://www.cnblogs.com/gdpdroid/p/4128177.html

  2)https://blog.csdn.net/u011240877/article/details/73440993

  3)https://baijiahao.baidu.com/s?id=1595521551376533549&wfr=spider&for=pc

线程池总结

原文:https://www.cnblogs.com/xy-ouyang/p/10466546.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!