欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

常用线程池及适用场景

程序员文章站 2022-07-12 11:12:12
...

线程池的工作队列

ArrayBlockingQueue: (有界队列) 是一个数组实现的有界阻塞队列,按FIFO排序量
LinkedBlockingQueue : 是一个基于链表实现的阻塞队列,按FIFO排序任务,可以设置容量,不设置使用Integer.Max_VALUE (不设置就是*队列)newFixThreadPool,newSingleThreadExecutor使用此队列
(吞吐量高于ArrayBlockingQueue)
DelayQueue : (延迟队列)是一个任务定时周期的延迟执行的队列。根据插入到队列的先后。newScheduledTreadPool线程池使用这个队列。
PriorityBlockingQueue : (优先级队列) 是具有优先级的*队列;
SynchronousQueue : (同步队列) 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程的调用移除操作,否则插入操作一直处于阻塞状态。吞吐量通常高于LinkedBlockingQuene newCachedThreadPool 线程池使用此队列。

线几种常用的线程池

newFixedThreadPool(固定数目的线程池)
newCachedThreadPool(可缓存线程的线程池)
newSingleThreadExecutor(单线程的线程池)
newScheduledThreadPool(定时及周期执行的线程池)

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

使用场景:
fixedThreadPool核心线程池等于最大线程池,当前的线程数能够比较稳定保证一个数。能够避免频繁回收线程和创建线程。故适用于处理cpu密集型的任务,确保cpu在长期被工作线程使用的情况下,尽可能少的分配线程,即适用长期的任务。

此方法的弊端是
到了线程池最大容量后,如果有任务完成让出占用线程,那么此线程就会一直处于等待状态,而不会消亡,直到下一个任务再次占用该线程。这就可能会使用*队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无线增大,使服务器资源迅速耗尽。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

使用场景
newCacehedThreadPool 的最大特点就是,线程数量不固定。只要有空闲线程空闲时间超过keepAliveTime,就会被回收。有新的任务,查看是否有线程处于空闲状态,如果不是就直接创建新的任务。故适用用于并发不固定的短期小任务。
此方法的弊端是
线程池没有最大线程数量限制,如果大量的任务同时提交,可能导致创线程过多会而导致资源耗尽。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

使用场景:
newSingleThreadExecutor 适用串行化任务,一个任务接着一个一个任务的执行。

 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

使用:

  ScheduledThreadPoolExecutor  scheduled = new ScheduledThreadPoolExecutor(2);
        scheduled.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                loge("time:");
            }
        }, 0, 40, TimeUnit.MILLISECONDS);
        //0表示首次执行任务的延迟时间,40表示每次执行任务的间隔时间
        //TimeUnit.MILLISECONDS执行的时间间隔数值单位

使用场景:
ScheduledThreadPoolExecutor 适用于定时操作一些任务

虽然Executors为我们提供了构造线程池的便捷方法,对于服务器程序我们应该杜绝使用这些便捷方法,而是直接使用线程池ThreadPoolExecutor的构造方法,避免*队列可能导致的OOM以及线程个数限制不当导致的线程数耗尽等问题。ExecutorCompletionService提供了等待所有任务执行结束的有效方式,如果要设置等待的超时时间,则可以通过CountDownLatch完成。

线程拒绝策略:
是系统超负荷运行的最后补救措施,,阻塞队列满了,最大线程池也满了,jdk提供丢弃策略有如下四种:

(1)ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException异常,丢弃任务,阻止系统正常工作。 (Executors类创建线程池的默认丢弃策略)
(2)ThreadPoolExecutor.DiscardPolicy:偷偷的丢弃任务,但是不抛出异常。
(3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃最老的任务(即队首马上要执行的任务),然后重新尝试执行任务(重复此过程)
(4)ThreadPoolExecutor.CallerRunsPolicy:只要线程池未关闭,直接去调用线程池的线程中处理该任务,可能引起性能的急剧下降。

execute方法与submit方法

(1)execute没有返回值;而submit有返回值,方便返回执行结果。
(2)submit方便进行Exception处理,由于返回参数是future,如果执行期间抛出了异常,可以使用future.get()进行捕获。

合理的选择线程池大小
CPU核数 * CPU核数 *(1+等待时间 / 计算时间)

线程池的几种状态:

Running
会接受新任务,处理阻塞队列的任务
调用 Shutdown() 切换为Shutdown 状态
调用 shutdownNow() 切换到STOP状态
Shutdown
不接受新任务,会处理阻塞队列的任务
阻塞队列的任务为空,线程池中的线程执行的任务也为空,变为TIDYING

Stop
不接受新任务,不会处理阻塞队列的任务,而且中断正在执行的任务。
线程池中正在执行的任务为空,则变为TIDYING
TIDYING
表明所有任务执行完毕,记录任务的数量为0
Terminated() 执行完毕,进入TERMINATED状态
TERMINATED
线程池彻底终止