• Executor框架(三)线程池详细介绍与ThreadPoolExecutor


    本文将介绍线程池的设计细节,这些细节与 ThreadPoolExecutor类的参数一一对应,所以,将直接通过此类介绍线程池。

    ThreadPoolExecutor类 简介

      java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

    ThreadPoolExecutor 的构造方法

    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);
    

    注意: 几个参数的大小范围,corePoolSize >= 0,maximumPoolSize >= 1 ,keepAliveTime >= 0(keepAliveTime为0时,表示线程永久存活,即使空闲很长时间,也不会撤销)

    线程池配置的各种参数以及策略

    1. corePoolSize (核心池的大小)与 maximumPoolSize(线程池最大线程数)

    • ThreadPoolExecutor 将根据 corePoolSize 和 maximumPoolSize 设置的边界自动调整池大小。
    • 池中线程的创建策略。 当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。
    • 数量固定的线程池。 如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。
    • 线程池数量任意。 如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。
    • 动态更改大小。 在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。

    2. 线程创建的时机

    • 默认情况下,即使核心线程最初只是在新任务到达时才创建和启动的
    • 也可以使用方法 prestartCoreThread() 或 prestartAllCoreThreads() 对其进行动态重写。如果构造带有非空队列的池,则可能希望预先启动线程。

    3. 线程创建的工厂方法 ThreadFactory

    • 使用 ThreadFactory 创建新线程。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。

    4. 保持活动时间 keepAliveTime

    • 如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止,直到池中的数量减少到核心数。这提供了当池处于非活动状态时减少资源消耗的方法。
    • 默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0,allowCoreThreadTimeOut(boolean) 方法调用后,也可将此超时策略应用于核心线程。

    5. BlockingQueue 任务队列

    • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
    • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
    • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
    • 任务队列的策略
      • 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
      • 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
      • 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

    6. 被拒绝的任务的处理策略

      当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:

    • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    • ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。即直接在 execute 方法的调用线程中运行被拒绝的任务;

    7. 队列维护

    • 方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。

    8. 钩子(hook)方法

    此类提供 protected 可重写的 beforeExecute(Runnable)afterExecute(Runnable, Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。


    @ Example1 钩子用法示例

    此类的大多数扩展可以重写一个或多个受保护的钩子 (hook) 方法。例如,下面是一个添加了简单的暂停/恢复功能的子类:

     class PausableThreadPoolExecutor extends ThreadPoolExecutor {
       private boolean isPaused;
       private ReentrantLock pauseLock = new ReentrantLock();
       private Condition unpaused = pauseLock.newCondition();
    
       public PausableThreadPoolExecutor(...) { super(...); }
     
       protected void beforeExecute(Thread t, Runnable r) {
         super.beforeExecute(t, r);
         pauseLock.lock();
         try {
           while (isPaused) unpaused.await();
         } catch(InterruptedException ie) {
           t.interrupt();
         } finally {
           pauseLock.unlock();
         }
       }
     
       public void pause() {
         pauseLock.lock();
         try {
           isPaused = true;
         } finally {
           pauseLock.unlock();
         }
       }
     
       public void resume() {
         pauseLock.lock();
         try {
           isPaused = false;
           unpaused.signalAll();
         } finally {
           pauseLock.unlock();
         }
       }
     }
    
    

    @ Example2 任务拒绝策略示例

      下面的例子是通过传入各种参数,配置创建了一个ThreadPoolExecutor线程池实例,并向此线程池提交多个任务,而且任务的数量大于线程池的承受数量。

    public class Test_29 {
       public static void main(String[] args) throws InterruptedException {
    	   SynchronousQueue<Runnable> queue = new SynchronousQueue<>();
    	   MyThreadFactory threadFactory = new MyThreadFactory();
           //创建一个线程池
    	   ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 3, 60,TimeUnit.SECONDS,queue, threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    
           //向线程池提交四个任务   
    	   for(int i=0;i<4;i++){
    		   MyRunnable myRunnable = new MyRunnable();
    		   poolExecutor.execute(myRunnable);
    	   }
    	   
    	   //关闭线程池
    	   poolExecutor.shutdown();
       }
    }
    
    //自定义的工厂方法
    class MyThreadFactory implements ThreadFactory{
    
    	static int number=0;
    	final String BASE_NAME = "poolthread_";
    
    	@Override
    	public Thread newThread(Runnable r) {
    		number++;
    		//自定线程池的创建线程的工厂方法,这里指定线程池中的每个线程命名:poolthread_i
    		Thread thread = new Thread(r,BASE_NAME+number);
    		System.out.println("线程池创建了一个线程:"+BASE_NAME+number);
    		return thread;
    	}
    }
    
    class MyRunnable implements Runnable{
    
    	@Override
    	public void run() {
    		try {
    			//休眠一秒,模拟线程的执行过程
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		//输出当前执行任务的线程的名称
            System.out.println("任务完成,执行任务的线程是:"+Thread.currentThread().getName());	
    	}
    }
    

    运行结果:

    线程池创建了一个线程:poolthread_1
    线程池创建了一个线程:poolthread_2
    线程池创建了一个线程:poolthread_3
    任务完成,执行任务的线程是:main
    任务完成,执行任务的线程是:poolthread_2
    任务完成,执行任务的线程是:poolthread_3
    任务完成,执行任务的线程是:poolthread_1

      注意此线程池的配置,队列用的是 SynchronousQueue ,即不会存储任务,都是要立即执行任务,所以此线程池的同一时间内只能最多接受3个任务。而例子一共提交了4个任务,由于拒绝任务的策略是ThreadPoolExecutor.CallerRunsPolicy,所以被线程池拒绝执行的任务,就由main线程执行了。
      拒绝任务的策略,除了JDK已经提供的四种外,还可以自定义策略,方法就是实现 RejectedExecutionHandler 接口


    Executors 中提供了三种常用的ThreadPoolExecutor的创建:

    1. FixedThreadPool 固定线程池

      固定线程池 的线程数量是固定的,由传入的参数决定。线程 keepAliveTime 为0,即不会因为空闲超时而关闭线程,同时队列是无边界的队列,不会发生任务丢弃。

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

    2. SingleThreadPoolExcutor 单线程池
      单线程池中线程数量固定为1.

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

    3. CachedThreadPool 缓存线程池

      缓存线程池的核心线程corePoolSize 数量为0,但是池中的最大线程数是 无边界。空闲超时为60s,队列用了SynchronousQueue,即任务是立即交付运行的。

     public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
  • 相关阅读:
    图论算法(三) 最短路SPFA算法
    图论算法(二)最短路算法:Floyd算法!
    图论算法(一)存图与STL第六弹——vector容器
    C++指针变量的基本写法
    杂记——深度优先搜索(dfs)与出题感想
    分治算法(二分查找)、STL函数库的应用第五弹——二分函数
    网站开发小技巧总结
    网站开发动态效果插件
    jquery获得ul下li的个数
    jquery的循环函数和点击事件绑定
  • 原文地址:https://www.cnblogs.com/jinggod/p/8489169.html
Copyright © 2020-2023  润新知