为什么使用线程池

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止消耗过多的内存。

在java.util.concurrent核心并发包下,JDK为我们提供了一个线程池工厂类—Executors。

Executors提供了几种类型的线程池:

public static ExecutorService newWorkStealingPool()
 
public static ExecutorService newSingleThreadExecutor()
 
public static ExecutorService newCachedThreadPool()
 
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

public static ExecutorService newFixedThreadPool(int nThreads)

 Executors提供的有六种线程池,常用的有四种如下:

newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

以newFixedThreadPool为例:

public static void main(String[] args) {
	//可缓冲线程池
	ExecutorService cache = Executors.newCachedThreadPool();
	//单一线程池
	ExecutorService single = Executors.newSingleThreadExecutor();
	//定长线程池
	ExecutorService fixed = Executors.newFixedThreadPool(4);
	//周期线程池
	ExecutorService scheduled = Executors.newScheduledThreadPool(3);
	for (int i = 0; i < 10; i++) {
		final int index = i;
		fixed.execute(new Runnable() {
		
			@Override
			public void run() {
				System.out.println("第"+Thread.currentThread().getName()+"个线程执行第"+index+"个任务");
				//System.out.println("第"+Thread.currentThread()+"个线程执行第"+index+"个任务");
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
		});
	}
	fixed.shutdown();
}

结果:

第pool-4-thread-2个线程执行第1个任务
第pool-4-thread-3个线程执行第2个任务
第pool-4-thread-1个线程执行第0个任务
第pool-4-thread-4个线程执行第3个任务
第pool-4-thread-2个线程执行第4个任务
第pool-4-thread-4个线程执行第5个任务
第pool-4-thread-3个线程执行第6个任务
第pool-4-thread-1个线程执行第7个任务
第pool-4-thread-2个线程执行第8个任务
第pool-4-thread-3个线程执行第9个任务

通过Thread.currentThread().getName()输出的pool-4-thread-3,这第一个数字(4)代表哪一种线程池,第二个数字(3)代表目前线程是第几个。

最后如果想要关闭线程池则通过shutdown()或者shutdownNow()来实现。

 1、shutDown() 

    当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。 

  2、shutdownNow() 

     执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。 
     它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

如何选择个人线程池

1.确认任务场景,是io密集还是cpu密集(大部分都是io密集型)来确定线程池类型
2.再确定使用场景,是优先保证任务执行还是保证服务性能,来确定线程池的任务队列和拒绝策略。
3.再确认是否需要自定义线程工厂(建议使用自定义线程工厂),在创建线程时打上标识与系统线程区分开。
4.最后根据任务类型,配置合理的线程池参数,没有最优的线程池参数,初期设置线程数建议在25-200。只能通过不断压测来不断调整来达到最优。

线程池启动不会初始化核心线程,可以通过prestartAllCoreThreads方法初始化所有核心线程。