一、什么是线程池?
一个线程池管理一组工作的线程,同时它还包括一个用于放置等待执行任务的任务队列(阻塞队列)。
二、线程池的工作原理
默认情况下,在创建线程池后,线程池中的线程数为0,当任务提交给线程池后处理策略如下:
1. 如果线程池中的数量小于 corePoolSize(核心线程池大小),即使线程池中的线程都处于空闲状态,也要创建一个新的线程来处理被添加的任务。
2. 如果线程池中的数量大于等于corePoolSize,但是缓冲队列 WorkQueue 未满,那么任务被放入到缓冲队列,则该任务会等待空闲线程将其去出去执行。
3. 如果线程池中的数量大于等于corePoolSize,而且缓冲队列 WorkQueue 已满,并且线程池中的数量小于最大线程数 MaximumPoolSize,则建新的线程来处理被添加的任务。
4. 如果线程池中的数量大于等于corePoolSize,而且缓冲队列 WorkQueue 已满,并且线程池中的数量等于最大线程数 MaximumPoolSize,则通过RejectedExceptionHandler所指定的策略(任务拒绝策略)来处理此任务。
即处理任务的优先级为:corePoolSize -> WorkQueue -> MaximumPoolSize,如果3这都满,则交给 RejectedExceptionHandler 处理被拒绝的任务。
5. 特别注意:在 corePoolSize 和 MaximunPoolSize 之间的线程会被自动释放。当线程数量大于corePoolSize时,如果某个线程空间超过KeepAliveTime,线程将被终止,直至线程池中线程数量不大于corePoolSize。这样,线程池可以动态调节池中线程的数量。
三、使用线程池的好处
1. 通过重复利用已创建的线程,减少在创建和销毁线程上所花的时间以及系统资源的开销。
2. 提高相应速度。当任务到达时,任务可以不需要等到线程创建就可以执行。
3. 提高线程的可管理性。使用线程池可以对线程统一的分配和监控。
4. 如果不使用线程池,系统会创建大量的线程导致消耗完系统内存。
四、线程池的创建
1. 接口与抽象类
Executors 类(推荐)
Executor 接口
ExecutorService 接口
AbstractExecutorService 抽象类
ThreadPoolExecutor 类(不推荐)
继承关系:Executor <- ExecutorService <- AbstractExecutorService <- ThreadPoolExecutor
2. Executors 类:(推荐使用)
它主要用来创建线程池。
Executors.newSingleThreadExecutor(); //创建容量为1 的线程池
Executors.newFixedThreadPool(int n); //创建固定容量大小的线程池
Executors.newCachedThreadPool(); //创建一个线程池,线程池最大容量为 Integer.MAX_VALUE(无界线程池)
1 public static void testExecutor() { 2 //ExecutorService service = Executors.newSingleThreadExecutor(); 3 ExecutorService service = Executors.newFixedThreadPool(2);//创建两个线程 4 service.submit(new Runnable() { 5 @Override 6 public void run() { 7 for (int i = 0; i < 10; ++i) { 8 try { 9 Thread.sleep(1000); 10 System.out.println("Executor1:" + i); 11 } catch (Exception e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 }); 17 18 service.submit(new Runnable() { 19 @Override 20 public void run() { 21 for (int i = 0; i < 10; ++i) { 22 try { 23 Thread.sleep(1000); 24 System.out.println("Executor2:" + i); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 } 28 } 29 } 30 }); 31 32 service.shutdown(); 33 while (!service.isTerminated()) { 34 try { 35 Thread.sleep(1000); 36 System.out.println("Wait for termination."); 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } 40 } 41 }
3. ThreadPoolExecutor 类
1 public ThreadPoolExecutor(int corePoolSize, 2 int maximumPoolSize, 3 long keepAliveTime, 4 TimeUnit unit, 5 BlockingQueue<Runnable> workQueue, 6 RejectedExecutionHandler handler);
corePoolSize:核心线程数。
maximunPoolSize:线程池最大线程数
keepAliveTime:默认情况下,线程池线程数大于 corePoolSize 后,keepAliveTime 才起作用,如果一个线程空闲时间达到 keepAliveTIme,则会终止,直到线程数不超过 corePoolSize
unit:参数keepAliveTime的 的时间参数
workQueue:阻塞队列,任务缓存队列,存放等待执行的任务
workQueue 的类型为BlockingQueue接口 ,有三种实现类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。在某次添加元素后必须等待其他线程取走后才能继续添加
ThreadFactor:线程工厂,用来创建线程
handler:表示当拒绝任务时的策略,有以下取值:
1.AbortPolicy:直接抛出异常(默认的)
2.DiscardPolicy:直接丢弃任务
3.DiscardOldestPolicy:丢弃队列中最旧(队头)的任务,并执行当前任务
4.CallerRunsPolicy:不用线程池中的线程执行,用调用者所在线程执行
ThreadPoolExecutor 有以下几个非常重要的方法:
execute()
submit()
shutdown()
shutdownNow()
execute() 与 submit() 的区别:
1. submit 有返回值,execute 没有返回值。所以说可以根据任务有无返回值选择对应的方法。
2. submit 方便异常的处理。如果任务可能会抛出异常,而且希望外面的调用者能够感知这些异常,那么就需要调用submit 方法,通过捕获Future.get抛出的异常。
shutdown() 与 shutdownNow() 的区别:
1.都是用来关闭线程池的。
2. shutdown 方法:此方法执行后不得向线程池再提交任务,如果有空闲线程则销毁空闲线程,等待所有正在执行的任务及位于阻塞队列中的任务执行结束,然后销毁所有线程。
3. shutdownNow 方法:此方法执行后不得向线程池再提交任务,如果有空闲线程则销毁空闲线程,取消所有位于阻塞队列中的任务,并将其放入List<Runnable>容器,作为返回值。取消正在执行的线程(实际上仅仅是设置正在执行线程的中断标志位,调用线程的interrupt 方法来中断线程)。