三十四、线程池之JDK自带的线程池

136 阅读5分钟

线程池之JDK自带的线程池

newFixedThreadPool

这个线程池的线程个数是固定的,而且最大核心工作线程数与最大工作线程数相同

线程池的构造函数:

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

创建线程池时传入的线程个数规定了最大线程数,而且此线程池使用LinkedBlockingQueue作为任务队列,此队列是无界的,也就是说此线程池不会拒绝任务。没有空闲线程时,任务放到队列中

线程池中的线程是懒加载的,只有当任务到来时,才会创建线程。如果线程已经执行完任务,会调用take方法从队列中取任务,因为此线程池中都是核心工作线程,所以在没有任务时也不会被销毁

总结:

  1. 线程个数固定
  2. 核心工作线程数等于最大工作线程数,说明没有任务时,线程不会被销毁
  3. LinkedBlockingQueue作为阻塞队列,没有边界,说明任务可以无限放,不会执行拒绝策略

newSingleThreadExecutor

这个线程池的线程个数固定是1个

线程池的构造函数:

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

static class FinalizableDelegatedExecutorService
	extends DelegatedExecutorService {
	FinalizableDelegatedExecutorService(ExecutorService executor) {
		super(executor);
	}
	protected void finalize() {
		super.shutdown();
	}
}

从构造函数中可以看出newSingleThreadExecutor线程池是单线程的

newSingleThreadExecutor被封装在FinalizableDelegatedExecutorService类中,FinalizableDelegatedExecutorService类提供了finalize方法,这个方法是在垃圾回收的时候被调用的

当线程池被垃圾回收的时候,会调用FinalizableDelegatedExecutorService的finalize方法,此方法中执行了shutdown方法,这个方法是销毁线程池中的工作线程。如果不执行shutdown方法,线程池被回收了,但是线程池创建的线程还在JVM中

所以,当在方法中创建局部线程池用于执行业务后,记得执行shutdown方法,以免大量线程没有被回收

FinalizableDelegatedExecutorService的finalize方法中的shutdown方法并不保险,最好在使用完newSingleThreadExecutor线程池后,执行一次shutdown方法

总结:

  1. 线程个数固定为1
  2. 核心工作线程数等于最大工作线程数,说明没有任务时,线程不会被销毁
  3. LinkedBlockingQueue作为阻塞队列,没有边界,说明任务可以无限放,不会执行拒绝策略

newCachedThreadPool

此线程池没有固定的线程个数

线程池的构造函数:

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

此线程池在没有空闲线程时,接收到任务会自动创建线程去执行

线程池中的线程在60s内一直空闲的话,线程会被销毁,在60s内有任务进来,线程就会去执行

所以只要有任务传给newCachedThreadPool线程池,任务不用进队列等待,一定会有线程去执行

总结:

  1. 线程个数不固定
  2. 核心工作线程数为0,就是说没有核心工作线程,都是普通工作线程
  3. 普通工作线程在空闲60秒后被销毁
  4. SynchronousQueue作为阻塞队列,没有边界,说明任务可以无限放,不会执行拒绝策略
  5. 当没有空闲线程时,任务过来,创建一个工作线程去执行

newScheduleThreadPool

此线程池没有固定的线程个数

此线程池可以延迟执行任务,也可以周期性执行任务

线程池的构造函数:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
	return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue());
}

此线程池通过DelayedQueue延迟队列实现定时执行任务的功能。延迟执行任务是从队列中拿到任务后执行,执行完成后删除任务;周期性执行任务是任务执行完成后放回延迟队列中,等待下次执行

线程池的使用:

public static void main(String[] args) {

	ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);

	// 任务正常执行
	pool.execute(() -> {
		System.out.println(Thread.currentThread().getName() + ": 1");
	});

	// 任务延迟5秒后执行
	pool.schedule(() -> {
		System.out.println(Thread.currentThread().getName() + ": 1");
	}, 5, TimeUnit.SECONDS);

	// 周期性执行,一开始任务延迟2秒后执行,后面都是每隔1秒执行一次
	pool.scheduleAtFixedRate(() -> {
		System.out.println(Thread.currentThread().getName() + ": 1");
	}, 2, 1, TimeUnit.SECONDS).;

	// 周期性执行,一开始任务延迟2秒后执行,后面都是每隔1秒执行一次
	pool.scheduleWithFixedDelay(() -> {
		System.out.println(Thread.currentThread().getName() + ": 1");
	}, 2, 1, TimeUnit.SECONDS);
}

其实scheduleAtFixedRate和scheduleWithFixedDelay方法还是有区别的

请看以下案例:

public static void main(String[] args) {

	ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);

	// 周期性执行,一开始任务延迟2秒后执行,后面都是每隔3秒执行一次
	pool.scheduleAtFixedRate(() -> {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": 1");
	}, 2, 1, TimeUnit.SECONDS);

	// 周期性执行,一开始任务延迟2秒后执行,后面都是每隔4秒执行一次
	pool.scheduleWithFixedDelay(() -> {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": 1");
	}, 2, 1, TimeUnit.SECONDS);
}

当任务本身的执行时间大于线程池执行任务的间隔时间,两个方法就会有差异

scheduleAtFixedRate方法:执行任务的间隔时间=任务本身执行花费的时间。相当于任务刚执行完,立马执行下一次。

scheduleWithFixedDelay方法:执行任务的间隔时间=任务本身执行花费的时间+线程池执行任务的间隔时间

总结:

  1. 核心工作线程数可以指定,但是最大工作线程数不固定
  2. 普通工作线程去队列中拿任务,如果没有任务,线程就被销毁
  3. DelayedWorkQueue作为阻塞队列,没有边界,说明任务可以无限放,不会执行拒绝策略

newWorkStealingPool

此线程池与上诉线程池有很大不同,不是基于ThreadPoolExecutor实现,而是基于ForkJoinPool实现

此线程池执行的是大任务,因为线程池会将大任务拆分成小任务,然后分配给多个线程去执行,最后将小任务执行结果进行汇总

此线程池中的每个线程都有自己的阻塞队列。而且空闲的线程会从其他线程的阻塞队列中获取任务执行。

线程池的构造函数:

public static ExecutorService newWorkStealingPool() {
	return new ForkJoinPool
		(Runtime.getRuntime().availableProcessors(),
		 ForkJoinPool.defaultForkJoinWorkerThreadFactory,
		 null, true);
}