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

java线程池知识

程序员文章站 2022-06-01 13:38:52
...

一,线程池创建方式

1.Executors

快捷创建线程池的方法:

        ExecutorService FixedThreadPool = Executors.newFixedThreadPool(10);

        ExecutorService CachedThreadPool = Executors.newCachedThreadPool();

        ExecutorService ScheduledThreadPool = Executors.newScheduledThreadPool(10);

        ExecutorService SingleThreadExceutor = Executors.newSingleThreadPool();

优点:快捷,简单
缺点:拒绝策略,阻塞队列闲置线程存活时间等参数,都是用Executors给的默认设置。无法满足特殊需求。
阿里巴巴开发手册,明确提出不允许使用此方法创建线程池,使用ThreadPoolExecutor创建线程 池,并手动设置线程池内线程数量,阻塞队列类型,大小。以及拒绝策略,方便后续维护和理解。

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

这里的任务队列就是使用的 LinkedBlockingQueue 且是*队列,最大值是 integer 的最大值。如果有很多的任务,会OOM异常。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

可以看出,各种线程的快捷创建方法,都是由ThreadPoolExecutor实现的。不如直接使用这个创建,别人一看就知道怎么回事, 也方便修改和配置。

2.ThreadPoolExecutor

LinkedBlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(20);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler AbortPolicy =new ThreadPoolExecutor.AbortPolicy();

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 2L,
                TimeUnit.SECONDS, queue, threadFactory, AbortPolicy);

这段代码,是自定义线程池,7个参数分别是

  • 1.线程最少线程数,初始线程数 (2)
  • 2.线程池内最大线程数 (4)
  • 3.闲置线程存活时间 (2L)
  • 4.存活时间单位 (TimeUnit.SECONDS)
  • 5.存放人物的队列 (queue)
  • 6.创建线程的工厂 (threadFactory)
  • 7.拒绝策略 (AbortPolicy)

二,线程池类型

1,FixedThreadPool

  • 容量固定的线程池。使用LinkedBlockingQueue作为任务队列,当任务数量大于线程池容量的时候,未执行的任务进入任务等待队列LinkedBlockingQueue中,当线程有空闲的时候,自动从队列中取出任务执行。
  • 使用场景: 大多数情况下,推荐使用的线程池。因为os系统和硬件是有线程上限限制的,不可能去无限的提供线程池操作。

2,CachedThreadPool

  • 缓存线程池。容量 0-Integer.MAX_VALUE,自动根据任务数扩容:如果线程池中的线程数不满足任务执行需求,则创建新的线程并添加到池中。生命周期默认60s,当线程空闲时长到60s的时候,自动终止销毁释放线程,移除线程池。

  • 使用场景 : 可用于测试最高负载量,用于对FixedThreadPool容量的参考。

注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT(默认60s)不活动,其会自动被终止

3,ScheduledThreadPool

  • 定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

4,SingleThreadExceutor

  • 单一容量线程池。

5,ForkJoinPool

  • 拆分合并,将一个大的任务,拆分成若干子任务,并最终汇总子任务的执行结果,得到大任务的执行结果。并行执行,采用工作窃取机制,更加有效的利用cpu资源。

5.1、主要类

  • ForkJoinPool : 用于执行Task。任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他未完成工作线程的队列的尾部获取一个任务。

  • ForkJoinTask:ForkJoin任务,提供在任务中执行fork()和join()操作的机制(二叉分逻辑),通常不直接继承ForkJoinTask类,而是继承抽象子类RecursiveTask(有返回结果) 或者 RecursiveAction (无返回结果)。

  • ForkJoinWorkerThread:ForkJoinPool 内部的worker thread,用来具体执行ForkJoinTask。内部有

  • ForkJoinPool.WorkQueue,来保存要执行的 ForkJoinTask。

  • ForkJoinPool.WorkQueue:保存要执行的ForkJoinTask。

5.2、工作窃取机制

1、大任务分割成N个子任务,为避免线程竞争,于是分开几个队列去保存这些子任务,并为每个队列提供一个工作线程去处理其中的任务。工作线程与任务队列一一对应。

2、如果A线程执行完自己队列中的所有任务,如果此时其他队列中还有未执行的任务,则A线程会去窃取一个其他队列的任务来执行。但是,此时两个线程同时访问可能会产生竞争问题,所以,任务队列设计成了双向队列。A线程窃取的时候,从另一端开始执行,尽可能的去避免线程竞争问题。

3、工作窃取机制,充分的利用线程资源,并尽可能的去避免线程间的竞争问题。但是,只能是尽可能避免,并不能规避。例如,双向队列只有一个任务。

三,阻塞队列

1,LinkedBlockingQueue

2,ArrayBlockingQueue

3,SynchronousQueue

4,PriorityBlockingQueue

四,拒绝策略

拒绝策略提供*接口 RejectedExecutionHandler ,其中方法 rejectedExecution 即定制具体的拒绝策略的执行逻辑。

jdk默认提供了四种拒绝策略:

1,ThreadPoolExecutor.AbortPolicy()

  • 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。

2,ThreadPoolExecutor.CallerRunsPolicy

  • 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大

3,ThreadPoolExecutor.DiscardPolicy

  • 直接丢弃,其他啥都没有

4,ThreadPoolExecutor.DiscardOldestPolicy

  • 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入

五,线程工厂

1,Executors.defaultThreadFactory

  • ThreadFactory是一个线程工厂。用来创建线程。这里为什么要使用线程工厂呢?其实就是为了统一在创建线程时设置一些参数,如是否守护线程。线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。它首先是一个接口类,而且方法只有一个。就是创建一个线程。
  public interface ThreadFactory {    
    Thread newThread(Runnable r);  
}  

2,Executors.privilegedThreadFactory

  • 如果在应用程序中需要利用安全策略来控制对某些代码库的访问,那么可以通过Executors.privilegedThreadFactory来制定自己的线程工厂,通过这种方式创建的线程,将与创建privilegedThreadFactory的线程拥有相同的访问权限,AccessControlContext,contextClassLoader.
    如果不使用privilegedThreadFactory,线程池创建的线程将从在需要新线程时调用excute 或submit 的客户端程序中继承访问权限。从而导致令人困惑的安全性异常。