创建线程池的方法有两种,一种是手动创建,一种是自动创建,本文将分别介绍这两种方式的简单使用。

一、自动创建线程池

  自动创建线程池主要是调用jdk提供的Executors类提供的方法。

1、newFixedThreadPool()方法

  它创建一个可重用固定个数的线程池,以共享的无界队列运行这些线程,构造方法如下:

/* @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

  此方法建了一个核心线程数和最大线程数相等的线程池,它的数量也就是我们传入的参数,需要注意的是,LinkedBlockingQueue队列容量是2147483647,假如任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终这些任务占用大量内存,会发生内存泄漏,会造成很严重的后果。

2、newSingleThreadExecutor()方法

  它创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO)执行,构造方法如下:

/*
        @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

  它和第一种方法的隐患是一样的,只不过在构造时将核心线程数和最大线程数直接设置成了1,但是任务队列仍是无界的LinkedBlockingQueue,仍可能会造成内存泄漏问题。

  它是可缓存线程池,先查看池中有没有以前建立的线程,如果有就直接使用,如果没有就创建一个新的线程加入池中,通常用于执行一些生存期很短的异步型任务,构造方法如下:

/*
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

  这个构造方法中使用的任务队列是SynchronousQueue,SynchronousQueue本身并不存储任务,而是对任务直接进行转发。但它的最大线程数被设置成了Integer.MAX_VALUE,由于CachedThreadPool 并不限制线程的数量,当任务数量特别多的时候,就可能会导致创建非常多的线程,最终超过了操作系统的上限而无法创建新线程,或者导致内存不足。

4、newScheduledThreadPool()方法

  它创建一个定长线程池,支持定时及周期性任务执行,构造方法如下:

/**
     * Creates a thread pool that can schedule commands to run after a
     * given delay, or to execute periodically.
     * @param corePoolSize the number of threads to keep in the pool,
     * even if they are idle
     * @return a newly created scheduled thread pool
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    
    /**
     * Creates a new {@code ScheduledThreadPoolExecutor} with the
     * given core pool size.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

5、newSingleThreadScheduledExecutor()方法

  它的构造方法如下:

/*
    *@return the newly created scheduled executor
     */
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

  第四和第五种方式都是使用ScheduledThreadPoolExecutor(int corePoolSize)作为核心构造方法,其实质是一种延迟队列,同时也定义了最大线程数为Integer.MAX_VALUE,同样有可能会出现内存泄漏的状况。

二、自定义线程池

1、ThreadPoolExecutor构造函数

  

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

  各参数的含义如下:

  • corePoolSize :核心线程数,即使空闲时仍保留在池中的线程数,除非设置allowCoreThreadTimeOut
  • maximumPoolSize : 池中允许创建的最大线程数
  • keepAliveTime :当线程数大于内核时,新进的任务会被存储到队列中,当这些线程等待时间超过这个阈值,就会被回收,等待执行新任务。
  • unit keepAliveTime参数的时间单位
  • workQueue : 用于在执行任务之前使用的队列。这个队列将仅保存execute方法提交的Runnable任务。
  • handler :阻止策略,执行被阻止时使用的处理程序,因为达到线程限制和队列容量

  线程池的执行逻辑是这样的:

  • 当线程池接收到提交的新任务,首先判断核心线程数目是否已经达到了限定值,如果没有,不论其它工作线程是否处于空闲状态,线程池都会创建一个新线程处理该任务
  • 当核心线程数目已满足限定值,工作队列未满时,线程池会将新任务放入执行队列进行等待
  • 当工作队列已满,当前线程数超过核心线程数,小于允许创建的最大线程数时,线程池会创建一个新线程处理该任务
  • 若当前线程数超过允许创建的最大线程数时,线程池会按照指定的策略处理无法执行的任务

2、BlockingQueue队列

  一般分为直接执行队列、有界任务队列、无界任务队列、优先任务队列。
  (1)SynchronousQueue直接执行队列:SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞。
  使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据设置的handler执行拒绝策略。
  (2)ArrayBlockingQueue有界队列:当有新的任务需要执行,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。
  它的执行逻辑与前面提到的部分基本一致,值得注意的是,如果队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下。
  (3)LinkedBlockingQueue无界队列:无界队列的构造函数如下,LinkedBlockingQueue队列容量是2147483647,前面提到,假如任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终这些任务占用大量内存,使用时要做好处理,避免出现资源耗尽的情况。

/**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    
    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

  (4)PriorityBlockingQueue优先任务队列:该队列的默认数组大小是11,但是其允许的最大容量也是很大的。在构造时,可以传入一个比较器Comparator,用来比较队列内各任务的优先级。

/**
     * 默认数组大小.
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    /**
     * 最大数组大小.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    
     /**构造函数
     * Creates a {@code PriorityBlockingQueue} with the specified
     * initial capacity that orders its elements according to their
     * {@linkplain Comparable natural ordering}.
     */
     
    public PriorityBlockingQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

3、Reject阻止策略

  当提交的任务数大于(workQueue.size() + maximumPoolSize),就会触发线程池的拒绝策略。
  RejectedExecutionHandler 是拒绝策略的接口,有四个实现类

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
  • CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行,即从哪个线程来的,哪个线程处理,;
  • DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
  • DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
public interface RejectedExecutionHandler {

    /**
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

4、自定义线程池实现

  (1)模拟自定义拒绝策略,实现RejectedExecutionHandler

public class MyRejected implements RejectedExecutionHandler{

	
	public MyRejected(){
	}
	
	@Override
	public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
		System.out.println("当前被拒绝任务为:" + r.toString());		
	}
}

  (2)task任务类

public class MyTask implements Runnable {

	private int taskId;
	private String taskName;
	
	public MyTask(int taskId, String taskName){
		this.taskId = taskId;
		this.taskName = taskName;
	}
	
	/**
	此处省略get/set方法
	**/

	@Override
	public void run() {
		try {
			System.out.println("run taskId =" + this.taskId);
			Thread.sleep(5*1000);
			System.out.println("end taskId =" + this.taskId);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
	}
	
	public String toString(){
		return Integer.toString(this.taskId);
	}

}

(3)自定义线程池实现

public static void main(String[] args) {
	
		ThreadPoolExecutor pool = new ThreadPoolExecutor(
				1, 				//coreSize
				2, 				//MaxSize
				60, 			//60
				TimeUnit.SECONDS, 
				new ArrayBlockingQueue<Runnable>(3)			//指定一种队列 (有界队列)
				, new MyRejected()                          //自定义拒绝策略
				//, new DiscardOldestPolicy()               //可以使用默认拒绝策略
				);
		
		MyTask mt1 = new MyTask(1, "任务1");
		MyTask mt2 = new MyTask(2, "任务2");
		MyTask mt3 = new MyTask(3, "任务3");
		MyTask mt4 = new MyTask(4, "任务4");
		MyTask mt5 = new MyTask(5, "任务5");
		MyTask mt6 = new MyTask(6, "任务6");
		
		pool.execute(mt1);
		pool.execute(mt2);
		pool.execute(mt3);
		pool.execute(mt4);
		pool.execute(mt5);
		pool.execute(mt6);
		
		pool.shutdown();
		
	}
控制台输出如下:
        run taskId =5
        当前被拒绝任务为:6
        run taskId =1
        run taskId =2
        run taskId =3
        run taskId =4
    
    分析如下:
    (1)当第1个任务到达时,线程池创建一个新线程去处理该任务
     (2)当第2个任务到达时,当前线程核心线程数已满足,但是未满足最大线程数,因此线程池会再创建一个新线程去处理该任务
     (3)当第3/4/5个任务到达时,因已满足最大线程数,线程池将它们放入队列等候处理
     (4)当第6个任务到达时,因已满足最大线程数,且队列已满,线程池无法处理该任务,执行自定义拒绝策略

三、springboot使用自定义线程池

  1、配置线程池

@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig {

    @Bean("taskExecutor")
    public Executor asyncServiceExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        //配置队列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 设置线程空闲时间,当超过核心线程之外的线程在空闲到达之后会被销毁(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("ThreadExcutor");
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}

  2、为@Async指定线程池

@Component
@Async("taskExecutor") //要和线程池bean中的对应
public class ThreadService {

    //期望此操作在线程池执行 ,不会影响原有的主线程
   
        try {
            Thread.sleep(5000);
           System.out.println("业务处理完成了....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}