java线程池详解

来源:互联网 发布:网络编辑部简介 编辑:程序博客网 时间:2024/05/18 18:17

一,线程池是指管理一组同构工作线程的资源池,包含两部分:work queue and work thread,工作队列保存所有等待执行的任务,工作线程的任务很简单:从工作队列中获取任务,执行任务,然后返回线程池并等待下一个任务。


二 ,线程池的管理:

        1,ThreadPoolExecutor->AbstractExecutorService->ExecutorService->Executor

          ThreadPoolExecutor构造函数

           public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              ThreadFactory threadFactory,

                              RejectedExecutionHandler handler) {}

             只有等工作队列满了之后,才会创建大于基本大小的线程。

         2, 设置线程池的大小:

              Ncpu= number of CPUs

              Ucpu=target CPU utilization , 0<=Ucpu<=1

              W/C= ratio of wait time to compute time

              要使处理器达到期望的使用率,线程池的最优大小为:

             Nthreads=Ncpu * Ucpu * (1+W/C)

             可以通过Runtime来获取CPU的数目:

              int N_CPU=Runtime.getRuntime().availableProcessors();

              对于计算密集型任务,线程池大小一般设置为N_CPU+1;

              对于包含I/O 操作或者其他阻塞操作的任务,由于线程并不会一直运行,因此线程池的规模应该更大。

              要正确的设置线程池大小,必须估算出等待时间和计算时间的比值

          3,饱和策略:

                AbortPolicy:  中止策略,默认的饱和策略,抛出未检查的RejectedExecutionException.

                DiscardPolicy: 抛弃策略会悄悄抛弃该任务。

                DiscardOldestPolicy: 抛弃下一个将被执行的任务,然后尝试重新提交任务。

                CallerRunsPolicy: 调用者运行,当线程池中的所有线程都占用,并且工作队列被填满之后,下一个任务将在调用execute时 

                                              在主线程中执行,由于执行需要一定的时间,因此主线程至少一段时间内不能提交任何任务,从而使得工

                                              作线程有时间来处理正在执行的任务,在这期间,主线程不会调用accept,因此到底的请求将保存在TCP 

                                             层的队列中而不是应用程序的队列中,如果持续过载,那么TCP层将最终发现它的请求队列被填满,因此      

                                             开始抛弃请求。从线程池到工作队列到应用程序再到TCP层,最终到达客户端,导致服务器在高负载下实

                                             现一种平缓的性能降低。

                  


         4,生命周期:

         JVM只有在所有(非守护)线程全部终止后才会退出,因此如果无法正确的关闭Executor,那么JVM将无法结束。

         shutdown(): 执行平缓的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成——包括那些还未开始的任务。

         shutdownNow(): 执行比较粗暴的关闭过程,它将尝试取消所有正在运行的任务,并且不再启动队列中尚未开始的任务。

         其实调用的都是thread.interrupt();

         一般这样关闭:

        exec.shutdown();

        exec.awaitTermination(long timeout, TimeUnit unit);  


三,Executors类提供了四种基本的线程池构造方式

      a, Executors.newFixedThreadPool(int nThreads)  

          线程数量nThreads是固定的,使用一个无界的LinkedBlockingQueue

      b, Executors.newSingleThreadExecutor()

           作用等同于Executors.newFixedThreadPool(1),但是唯一区别就是它返回的是一个保证不会被额外线程重构的一个Executor

      c,  Executors .newCachedThreadPool() 等同于

           new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue<Runnable>());

           These pools will typically improve the performance of programs

           that execute many short-lived asynchronous tasks.

           SynchronousQueue不是一个真正的队列,只是一种线程之间作为信息移交的一种机制,要将一个元素放入SynchronousQueue,必须有另一个线程正在等待接受这个元素,如果没有线程正在等待,

   并且线程池的当前大小小于最大值,那么将创建一个新的线程,否则根据饱和策略,这个任务将被拒绝。

       d, Executors .newScheduledThreadPool(int corePoolSize)

           Creates a thread pool that can schedule commands to run after a

           given delay, or to execute periodically。

           默认为一个无界的DelayedWorkQueue()


四,注意点:

1,线程池初始化完了之后,实际线程数为0,只有当有任务执行时,才会创建线程,根据执行任务数的增多,线程池中的线程数逐步达到coreSize,然后处于稳定状态,当线程池中的线程都处于繁忙状态时,后续任务会进入等待队列中,线程会从队列中获取任务来执行,当队列被填满时,就会创建新的线程,直到到达maxSize, 如果还是不行,就会执行饱和策略。

2,线程池默认创建的为非守护线程,也就意味着即使发生异常,JVM也无法自动退出,必须关闭线程池或者调用System.exit()强制退出。意味着线程池是无法被GC掉的,只有调用了shutdown()之后,才会被GC。

3,如果一个线程池没有被程序再引用,并且没有剩余的线程,那么它会自动shutdown. 如果你想确保没有被引用的线程池一定会被回收,即使用户忘记调用shutdown(),那么你可以通过设置keep-alive times来安排空闲的线程最终死亡,coreSize设置为0或者设置ThreadPoolExecutor#allowCoreThreadTimeOut(true)

4,子类线程池可以定制化,执行前设置ThreadLocal,或者记录log等等。

       protected void beforeExecute(Thread t, Runnable r) { }

       protected void afterExecute(Runnable r, Throwable t) { }

0 0