Java并发之执行器 —— 线程池ThreadPool

优势

  1. 构建一个新的线程是有一定代价的, 因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池
  2. 减少并发线程的数目

Executors

执行器( Executors) 类有许多静态工厂方法用来构建线程池

方法 描述
newCachedThreadPool 必要时创建新线程;空闲线程会被保留 60 秒
newFixedThreadPool 该池包含固定数量的线程;空闲线程会一直被保留
newSingleThreadExecutor 只有一个线程的 “ 池”, 该线程顺序执行每一个提交的任务(类似于 Swing 事件分配线程)
newScheduledThreadPool 用于预定执行而构建的固定线程池, 替代 java.util.Timer
newSingleThreadScheduledExecutor 用于预定执行而构建的单线程 “ 池”
newWorkStealingPool (1.8新增) 创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。并行级别对应于主动参与或可用于进行任务处理的最大线程数。线程的实际数量可以动态地增长和收缩。工作窃取池不能保证执行提交的任务的顺序。

newCachedThreadPool 方法构建了一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。newFixedThreadPool 方法构建一个具有固定大小的线程池。 如果提交的任务数多于空闲的线程数,那么把得不到服务的任务放 置到队列中。当其他任务完成以后再运行它们。newSingleThreadExecutor 是一个退化了大小为 1 的线程池: 由一个线程执行提交的任务,一个接着一个。这3个方法返回实现了ExecutorService 接口ThreadPoolExecutor 类的对象。

再将一个 Runnable 对象或 Callable 对象提交给 ExecutorService得到Future

ExecutorService

  • Future<?> submit(Runnable task)  使用这样一个对象来调用 isDone、 cancel 或 isCancelled。但是, get 方法在完成的时候只是简单地返回 null。
  • Future<T> submit(Runnable task, T result) Future 的 get 方法在完成的时候返回指定的 result 对象。
  • Future<T> submit(Callable<T> task) 返回的 Future 对象将在计算结果准备好的时候得到它。
  • void shutdown() 关闭服务, 会先完成已经提交的任务而不再接收新的任务。

ThreadPoolExecutor

以Executors的newCachedThreadPool为例,观察内部实例化了一个ThreadPoolExecutor对象。

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

观察ThreadPoolExecutor构造方法

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

从结构关系上分析ThreadPoolExecutor继承了AbstractExecutorService抽象类,并实现了ExecutorService接口

从构造参数上分析

  • corePoolSize: corePoolSize线程池中要保持的数量,即使它们是空闲的,除非设置了allowCoreThreadTimeOut属性为true
  • maximumPoolSize:最大允许在池中允许的最大线程数。
  • keepAliveTime:当线程的数量大于核心时,这是空闲线程在终止之前等待新任务的最大时间。(超过核心线程数的线程如果长时间获得不到新任务就会终止掉)
  • unit : keepAliveTime的单位 如TimeUnit.SECONDS 代表秒
  • workQueue:工作队列(实现BlockingQueue接口的阻塞队列)用于在执行任务之前保存任务的队列。此队列只保留execute方法提交的Runnable任务。常使用的阻塞队列LinkedBlockingQueue、SynchronousQueue、ArrayBlockingQueue

线程的排队策略与阻塞队列相关:

LinkedBlockingQueue:当任务数大于核心线程数时,新任务添加到队列中,当队列容量达到最大时,将创建新线程,直到达到最大线程数maximumPoolSize。(基于链表实现的一个阻塞队列,默认容器大小Integer.MAX_VALUE。)

SynchronousQueue: 当任务数大于核心线程数时,直接创建新的线程直到达到最大线程数maximumPoolSize。(一个没有内部容量的阻塞队列,其中每个插入操作必须等待另一个线程执行相应的移除操作。)

ArrayBlockingQueue : 同LinkedBlockingQueue。(基于数组实现的一个阻塞队列,需要指定容器大小。)

  • threadFactory:创建新线程时要使用的工厂。
  • handler : 处理程序在执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量。

handler有以下四种策略:

ThreadPoolExecutor.AbortPolicy():丢弃任务并抛出RejectedExecutionException异常。 (ThreadPoolExecutor默认)
ThreadPoolExecutor.CallerRunsPolicy():由调用线程处理该任务 。
ThreadPoolExecutor.DiscardPolicy():也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

使用

newFixedThreadPool会提前创建好固定数量的线程池

		ThreadPoolExecutor pool =(ThreadPoolExecutor)Executors.newFixedThreadPool(3);
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1

newCachedThreadPool 源码中初始线程数为0,当使用时会立即创建线程

		ThreadPoolExecutor pool =(ThreadPoolExecutor)Executors.newCachedThreadPool();
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});
		pool.submit(()->{
			System.out.println(Thread.currentThread().getName());
		});

pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2

小结

使用ThreadPoolExecutor构造方法去生成线程池更加灵活,可以参考Spring的ThreadPoolTaskExecutor的实现。

猜你喜欢

转载自blog.csdn.net/zl_momomo/article/details/81451053