• 这两天学的线程池归纳


    1 线程池 ---》接口 Executor

    2 线程池 ---》接口 ExecutorService

    final ExecutorService poolExecutor = Executors.newFixedThreadPool(threadNum);

    poolExecutor.submit(new ConvertOne(latch,seperateMap,dbname));

    poolExecutor.shutdown();
    shutdown()方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务的启动并试图停止当前正在执行的任务。在终止后,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService以允许回收其资源。
    例子:网络服务的简单结构


    3 线程池 ---》类 ThreadPoolExecutor
    由于内容较多没有一一研究,只看了较常用的 ThreadPoolExecutor ,所以在这里做个介绍。ThreadPoolExecutor 的继承关系如下。
    Executor->ExecutorService->AbstractExecutorService->ThreadPoolExecutor
    已实现的接口: Executor, ExecutorService

    每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程)。

    关于ThreadPoolExecutor的 execute方法:
    public void execute(Runnable command)
    这里给出源码:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
    ctl是一个重要的变量,主要包装两个重要的概念:一是workerCount:effective number of threads,二是runState:  indicating whether running, shutting down 
    
    RUNNING可以接受新的task,并且可以处理queue中的task,SHUTDOWN不可以接受新的task,但是可以处理queue中的task,其他的全都不可以。
    
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) { 判断线程数是否小于corePoolSize。否则进入②
    if (addWorker(command, true))
    return;
    c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) { //② 判断线程是否是running状态,并且阻塞队列的容量没有达到上限
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
    reject(command);//如果此处状态不是RUNNING,也不是SHUTDOWN,或者removeFirstOccurrence(队列中移除第一个阻塞的任务,返回ture)那么,则拒绝任务
    else if (workerCountOf(recheck) == 0)
    addWorker(null, false);//由于任务放到了BlockingQueue中,此处,在Worker中,不添加task,而是运行任务时,从queue取出task
    }
    else if (!addWorker(command, false))//除了以上情况以外,比如BlockingQueue饱和了,线程池容量也饱和了,执行饱和策略,默认为AbortPolicy,拒绝任务
    reject(command);
    }
    

      

    常用两个阶段来关闭ExecutorServise
    ( 1 shutdown 拒绝传入任务 ,当所有已提交任务执行完后,就关闭。如果已经关闭,则调用没有其他作用。 抛出:
            SecurityException - 如果安全管理器存在并且关闭,此 ExecutorService 可能操作某些不允许调用者修改的线程。
    (2 在!pool.awaitTermination(60, TimeUnit.SECONDS) 任务还没有执行完,取消所有遗留的任务 shutdownNow

    关于构造的参数 corePoolSize ,maximumPoolSize的说明
    注意1:在新任务被提交时,如果运行的core线程少于corePoolSize,才创建新core线程。并不是一开始就创建corePoolSize个core线程。
    注意2:"如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程"
    注意3:如果两个相同则固定大小的线程池(这时候可以用blockingQueue 进行排队,用来传输和保持提交的任务,使用队列与线程池的大小进行交互)
    注意4: maximumPoolSize设置成 Integer.MAX_VALUE,则允许使用任何数量的并发任务。无界
    注意5:核心线程即core线程,只有当前线程数小于等于corePoolSize时,这时的线程才叫核心线程。

    构造函数:

    (1)public ThreadPoolExecutor(int corePoolSize, //池中所保存的线程数,包括空闲线程。
                              int maximumPoolSize,//池中允许的最大线程数。
                              long keepAliveTime,//当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
                              TimeUnit unit,//参数的时间单位。
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)
    
    使用ThreadFactory创建新线程,如果没有另外说明,则使用 Executors.defaultThreadFactory() 创建线程,他们在同一个ThreadGroup中, 并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。
    
    (2)public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)
    
    使用workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。 
     线程池所使用的缓冲队列,改缓冲队列的长度决定了能够缓冲的最大数量。
    拒绝任务:拒绝任务是指当线程池里面的线程数量达到 maximumPoolSize 且 workQueue 队列已满的情况下被尝试添加进来的任务。
    
    (3)public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
    使用handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。 

    关于线程处于非活动状态如何减少资源消耗:【空闲线程的回收】
    注意1:setKeepAliveTime(long, java.util.concurrent.TimeUnit)用于设置空闲线程最长的活动时间,
        即如果空闲时间超过设定值,就停掉该线程,对该线程进行回收。
        该策略默认只对非内核线程有用(即当前线程数大于corePoolSize),
        可以调用allowCoreThreadTimeOut(boolean)方法将此超时策略扩大到核心线程。 设置适当保持活动时间,使用0核心线程的下边界和/或设置 allowCoreThreadTimeOut(boolean)。
    注意2:如果把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。

    关于blockingQueue 进行排队与线程池大小进行交互:

    为什么要与线程池大小进行交互,用于传输和保持提交的任务,
    (1、 corePoolSize, maximumPoolSize的设置
        * 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
             * 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
             * 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
    (2、排队的三种通用策略
      直接提交【 默认选项 synchronousQueue 】此策略允许线程无界的增长。
    任务直接提交给线程,而不保持它们。
    如果不存在立即运行任务的线程,会构造新的线程,通常需要设置maximumPoolSize无界,否则无法创建新线程,导致拒绝新任务的提交。
      无界队列【例如,没有预定容量的LinkedBlockingQueue】此策略适合web服务器类型,每个任务独立于其他任务。任务互不影响。允许队列无线的增长,适合处理瞬态突发请求。
    创建的线程永远不会超过corePoolSize 但是maximumPoolSize就失效了。
      有界队列【例如,ArrayBlocking】
     队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。
     使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。Queue】此策略有助于防止资源耗尽,但是可能较难调整和控制。

    Java 线程池 ThreadPoolExecutor.的拒绝策略

    在 ThreadPoolExecutor 里面定义了 4 种 handler 策略,分别是
    (1)CallerRunsPolicy :这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降。
    (2)AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。(默认的)RejectedExecutionException
    (3)DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。
    (4)DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。
    关于这个给出源码:

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
    e.getQueue().poll();
    e.execute(r);
    }
    }
    

    扩展RejectedExecutioHandler接口,自定义拒绝策略

    先看下RejectedExecutionHandler接口吧:
    public interfaceRejectedExecutionHandler{
        voidrejectedExecution(Runnable r,ThreadPoolExecutor executor);
    }
    再举个例子吧:

     1 public classTestRejectHandler {
     2     class MyTask implements Runnable{
     3        @Override
     4        public void run() {
     5            System.out.println("Thread ID:"+Thread.currentThread().getId());
     6            try{
     7               Thread.sleep(1000);
     8            }catch(InterruptedException e){
     9               e.printStackTrace();
    10            }
    11        }
    12     }
    13     public void test(){
    14        ThreadPoolExecutor executor= newThreadPoolExecutor(5, 5,
    15               0L, TimeUnit.MILLISECONDS,
    16               newLinkedBlockingQueue(10),
    17               Executors.defaultThreadFactory(),
    18               newRejectedExecutionHandler() {  
    19                   @Override
    20                   public voidrejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    21                      System.out.println(r.toString()+" 被抛弃了");
    22                   }
    23               });
    24        MyTask task= newMyTask();
    25        for(int i=0;i<20;i++){
    26            executor.submit(task);
    27        }
    28        executor.shutdown();
    29     }


    最后给出一个简单的利用线程池的例子

    public class ThreadPoolTermination {
    	public static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS,
    			new LinkedBlockingQueue<Runnable>(10), new SolrjNamedThreadFactory("Termination"),
    			new ThreadPoolExecutor.CallerRunsPolicy());
    	//SolrjNamedThreadFactory监控线程
    	//CallerRunsPolicy 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序
    
    	static class TestThread implements Runnable {
    		int i;
    		public TestThread(int i) {
    			this.i = i;
    		}
    		@Override
    		public void run() {
    			try {
    				String thrdName = Thread.currentThread().getName();
    				//dosomethind-------------这里写线程需要做的事
    				System.out.println("当前线程:"+thrdName+"   "+i);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	public static void main(String[] args) {
    		for (int i = 0; i < 200; i++) {
    			threadPool.execute(new TestThread(i));
    		}
    		/*下列方法分两个阶段关闭 ExecutorService。第一阶段调用 shutdown 拒绝传入任务,然后等10秒后,
    		任务还没执行完成,就调用 shutdownNow(如有必要)取消所有遗留的任务*/
    		threadPool.shutdown();
    		try {
    			while (!threadPool.awaitTermination(10, TimeUnit.MILLISECONDS)) {
    				System.out.println("waiting for thread pool to terminate...");
    			}
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    			threadPool.shutdownNow();
    		}
    		System.out.println("done");
    	}
    }
    

      总结一下:

    一个任务通过 execute(Runnable) 方法被添加到线程池,任务就是一个 Runnable 类型的对象,任务的执行方法就是 Runnable 类型对象的 run() 方法。

    当一个任务通过 execute(Runnable) 方法欲添加到线程池时,线程池采用的策略如下:

    1. 如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

    2. 如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。

    3. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于maximumPoolSize ,建新的线程来处理被添加的任务。

    4. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。

    处理任务的优先级为:

    核心线程 corePoolSize 、任务队列 workQueue 、最大线程 maximumPoolSize ,如果三者都满了,使用 handler处理被拒绝的任务。当线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。

  • 相关阅读:
    第二次结对作业
    第一次结对作业
    第二次个人编程
    第一次编程作业
    第一篇随笔
    个人总结
    第三次个人作业
    第二次结对作业
    第一次结对作业
    第二次编程
  • 原文地址:https://www.cnblogs.com/zyxs/p/8820351.html
Copyright © 2020-2023  润新知