什么是线程池?

线程池有什么优势?

有以下优势:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立刻执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果是无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进统一的分配,调优和监控。

深入学习Java中的线程池-LMLPHP

线程池的使用

Executors创建指定类型的线程池

内部实现基于ThreadPoolExecutor,固定参数。

newSingleThreadExecutor

ExecutorService es1 = Executors.newSingleThreadExecutor();

newFixedThreadPool

ExecutorService es2 = Executors.newFixedThreadPool(3);

newCachedThreadPool

这种类型的线程池特点是:

ExecutorService es3 = Executors.newCachedThreadPool();

newScheduledThreadPool

ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);

newSingleThreadScheduledExecutor

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

ThreadPoolExecutor创建自定义线程池

创建自定义线程池,参数可自定义配置。

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue);

可以看到,其需要如下几个参数:

corePoolSize:线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法预创建线程,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列(workQueue)当中;

maximumPoolSize:线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变最大线程数。

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。当线程池中的线程数大于corePoolSize且一个线程空闲的时间达keepAliveTime,则该线程会被销毁。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

unit:参数keepAliveTime的时间单位,有7种取值,在java.util.concurrent.TimeUnit类中:

  1. imeUnit.DAYS             天
  2. TimeUnit.HOURS          小时
  3. TimeUnit.MINUTES         分钟
  4. TimeUnit.SECONDS        秒
  5. TimeUnit.MILLISECONDS   毫秒
  6. TimeUnit.MICROSECONDS  微妙
  7. TimeUnit.NANOSECONDS   纳秒

任务队列-workQueue:一个阻塞队列 BlockingQueue,用来存储等待执行的任务(Runnable),这个参数的选择也很重要,会对线程池的运行过程产生重大影响,阻塞队列有以下几种选择,都是BlockingQueue的实现:

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
  3. SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
  4. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  5. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
  6. LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
  7. LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

ArrayBlockingQueuePriorityBlockingQueue使用较少,一般使用LinkedBlockingQueueSynchronousQueue。线程池的排队策略与BlockingQueue有关。

线程工厂-threadFactory:用于设置创建线程的工厂 ThreadFactory,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等
Executors 框架已经为我们实现了一个默认的线程工厂:

/**
 * The default thread factory.
 */
private static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
 
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

拒绝策略-handler:表示当拒绝处理任务时的策略 RejectedExecutionHandler,有以下四种取值:默认策略AbortPolicy

RejectedExecutionHandler defaultHandler = new AbortPolicy();
  • AbortPolicy:直接抛出异常(默认)。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
  • 自定义策略:实现RejectedExecutionHandler接口。
  • workers:线程池中的工作线程都放在此集合 HashSet中,Worker是线程池中的线程,而Task虽然是runnable,但是并没有真正执行,只是被Worker调用了run方法。

    Worker类本身既实现了Runnable,又继承了AbstractQueuedSynchronizer(以下简称AQS),所以其既是一个可执行的任务,又可以达到锁的效果,Worker实现的AQS为不可重入锁,为了在获得worker锁的情况下再进入其它一些需要加锁的方法。

    HashSet<Worker> workers = new HashSet<Worker>();
    

    mainLock:线程执行任务时候使用的加锁。

    ReentrantLock mainLock = new ReentrantLock();
    

    termination:加锁使用的条件

    Condition termination = mainLock.newCondition();
    

    线程池的工作原理

    下面来描述一下线程池工作的原理,同时对上面的参数有一个更深的了解。其工作原理流程图如下:
    深入学习Java中的线程池-LMLPHP

    功能线程池

    上面使用线程池的方法太麻烦?其实Executors已经为我们封装好了 4 种常见的功能线程池,如下:

    定长线程池(FixedThreadPool)
    定时线程池(ScheduledThreadPool )
    可缓存线程池(CachedThreadPool)
    单线程化线程池(SingleThreadExecutor)
    

    定长线程池(FixedThreadPool)

    创建方法的源码:

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

    特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    应用场景:控制线程最大并发数。

    示例

    		// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		fixedThreadPool.execute(task);
    

    定时线程池(ScheduledThreadPool )

    创建方法的源码:

    		private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    		    return new ScheduledThreadPoolExecutor(corePoolSize);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize) {
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue());
    		}
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(
    		        int corePoolSize, ThreadFactory threadFactory) {
    		    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize,
    		                                   ThreadFactory threadFactory) {
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue(), threadFactory);
    		}
    

    特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
    应用场景:执行定时或周期性的任务。

    示例:

    		// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    		scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
    

    可缓存线程池(CachedThreadPool)

    创建方法的源码:

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

    特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
    应用场景:执行大量、耗时少的任务。

    示例

    		// 1. 创建可缓存线程池对象
    		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		cachedThreadPool.execute(task);
    

    单线程化线程池(SingleThreadExecutor)

    创建方法的源码:

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

    特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
    应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

    示例

    		// 1. 创建单线程化线程池
    		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		singleThreadExecutor.execute(task);
    

    深入学习Java中的线程池-LMLPHP
    参考:Android多线程:线程池ThreadPool全面解析
    Java 多线程:彻底搞懂线程池
    java中的线程池

04-11 10:04