• 多线程—4种线程池


    线程池中的接口和类:

    《一》ThreadPoolExecutor

    线程池中最核心的一个类,java.util.concurent.包下

    1:ThreadPoolExecutor类中几个重要的属性:

    volatile int runState;

    static final int RUNNING = 0;

    static final int SHUTDOWN = 1;

    static final int STOP = 2;

    static final int TERMINATED = 3;

    private volatile int corePoolSize;

    private volatile int maximumPoolSize;

    private volatile int poolSize;

    private volatile ThreadFactory threadFactory;

    private int largestPoolSize;

    private final class Worker implements Runnable { 

    private final ReentrantLock mainLock = new ReentrantLock();

    2:在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);

        ...}

    发现前面三个构造器都是调用的第四个构造器进行的初始化7个工作基本参数。

    int corePoolSize

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

    int maximumPoolSize

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

    long keepAliveTime,

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

    TimeUnit unit

    参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

    TimeUnit.DAYS;          //天

    TimeUnit.HOURS;          //小时

    TimeUnit.MINUTES;         //分钟

    TimeUnit.SECONDS;        //秒

    TimeUnit.MILLISECONDS;      //毫秒

    TimeUnit.MICROSECONDS;      //微妙

    TimeUnit.NANOSECONDS;       //纳秒

    BlockingQueue<Runnable> workQueue

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

    ArrayBlockingQueue;

    LinkedBlockingQueue;

    SynchronousQueue;

    ThreadFactory threadFactory

    线程工厂,主要用来创建线程

    RejectedExecutionHandler handler

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

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

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

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

    4:ThreadPoolExecutor.CallerRunsPolicy 在这种方式,中任务将有调用者线程去执行。 

    3:ThreadPoolExecutor类中几个非常重要的方法:

    execute()方法

    实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

    submit()方法

    是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。

    shutdown()和shutdownNow()

    是用来关闭线程池的。

    还有很多其他的方法:

    比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法。

    《二》Executors类

    在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

    1:newCachedThreadPool()可缓存线程池

    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

    底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)

    通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。

    适用:执行很多短期异步的小程序或者负载较轻的服务器

    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {

            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

            60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);}

    2:newFixedThreadPool() 定长线程池

     创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置,如Runtime.getRuntime().availableProcessors()。 

    底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列

    通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)

    适用:执行长期的任务,性能好很多

    public static ExecutorService newFixedThreadPool(int nThreads) {

         return new ThreadPoolExecutor(nThreads, nThreads,

              0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());}

    3:newSingleThreadExecutor() 单线程化线程池

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

    底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列

    通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)

    适用:一个任务一个任务执行的场景

    public static ExecutorService newSingleThreadExecutor() {

            return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,

                      0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));} 

    4:newScheduledThreadPool() 周期性线程池

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

    底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列

    通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构

    适用:周期性执行任务的场景

    public static ScheduledExecutorService newScheduledThreadPool(

                int corePoolSize, ThreadFactory threadFactory){

    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);} 

    返回的是接口

    前3个都是new的 ThreadPoolExecutor 这个核心类,返回的都是ExecutorService 接口.

    最后一个不一样。

    《三》线程池的大小配置

    一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数)

    • 如果是CPU密集型应用,则线程池大小设置为N+1
    • 如果是IO密集型应用,则线程池大小设置为2N+1

    如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理,具体还需自行测试验证。

    但是,IO优化中,这样的估算公式可能更适合:

    最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

    因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

    下面举个例子:

    比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

    最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

    刚刚说到的线程池大小的经验值,其实是这种公式的一种估算值。

  • 相关阅读:
    乌龟棋
    Cut the Sequence
    [NOI2001]炮兵阵地
    Fence
    环路运输
    查找并替换字符串 Find And Replace in String
    最大交换 Maximum Swap
    丑数问题 Ugly Number
    二叉树最大宽度 Maximum Width of Binary Tree
    距离为K的节点 All Nodes Distance K in Binary Tree
  • 原文地址:https://www.cnblogs.com/domi22/p/8046796.html
Copyright © 2020-2023  润新知