线程池的学习
线程池:由于在平常的线程使用中,会频繁的创建和销毁线程,因为我们之前所创建的是单个的线程,使用完毕后就自动销毁,这样会大大的降低系统的效率,因为创建和销毁线程都需要时间。
所以急需一种方法,在线程执行完毕任务后,并不会立即的销毁,而是还可以继续执行下一个任务。Java中线程池就实现了这种想法。
1.首先我们来了解一下这几个线程池中抽象类,接口之间的关系:
- Executor(接口):是一个顶层接口; 里面只有一个方法:
void execute(Runnable runnable):用来执行传递进来的任务的,没有返回值。
- ExecutorService(接口):继承了Executor接口,并声明了一些方法:
Future<?> submit ( Runnable / Callable):用来执行任务,并带有返回值;
Void shutDown():关闭线程池;以及invoke方法;
- AbstractExecutorService(抽象类):实现了Executor,ExecutorService接口,方法主要是ExecutorService接口里的方法;
Future<?> submit ( Runnable / Callable):用来执行任务,并带有返回值;
void shutDown():关闭线程池;以及invoke方法;
- ThreadPoolExecutor(类);继承AbstractExecutorService抽象类,也实现了Executor,ExecutorService接口(传递性);几个重要的方法:
void execute(Runnable):执行任务,其实是Executor中声明的方法,通过该方
法可以向线程池中提交一个任务,交由线程池去执行。;
Future <?> submit(Runnable / Callable<>):执行任务,并带有返回值,其实是ExecutorService中声明的方法,也是向线程池提交任务 的,实际实现时还是调用了execute()方法,只不过利用Future来获取任务的执行结果。
Shutdown():用来关闭线程池。
(ThreadPoolExecutor构造方法中参数的意义):
corePoolSize : 核心池的大小。在创建线程池后,默认情况下线程池中没有一个线程,而是等待任务来后,才会创建线程去执行任务。除 非调用了prestartAllCoreThread()和平pestartCoreThread()方法,即预创建线程(创建所有corepollsize个线程或者一个线程)。当线 程数达到了corepoolsize再有任务过来时,就会把任务放入缓存队列中。
maximumPoolSize:线程池中的最大线程数。
KeepAliveTime:表示线程在没有任务时最多保持多久会结束。默认情况下只有当线程池中的线程数大于corepoolsize时才会起作用。
Unit:表示KeepAliveTime的时间单位,有7种取值。
workQueue:一个阻塞队列,用来存储等待执行的任务(任务缓存队列)。一般的阻塞队列会有以下几种选择:ArrayBlockingQueue, LinkedBlockingQueue,SynchronousQueue三种形式一般采用 LinkedBlockingQueue 和 SynchronousQueue。
ThreadFactory: 线程工厂,主要用来创建线程。
Handler:表示拒绝处理任务的策略。
2.线程池的几个状态:
由一个 volatile变量和4个 static final int 变量表示。
- Volatile int runState :表示线程的状态,volatile关键字保证了线程之间对runState的可见性。(Volatile变量取值为下面几个static变量)
- Static final int Running (执行); static final int shutdown(线程池关闭);static final int stop(阻塞);terminated(终结)
- 执行状态的切换:
- Running(执行状态):当创建好线程池,初始时;
- Shutdown(状态):当调用了shutdown()方法,线程池会处于该状态。此时线程池将不会接受新任务,而是等待当前所有任务执行完毕;
- Stop(状态):当调用了shutdownnow()后,线程池将不会接受新的任务,并去尝试终止正在执行的任务;
(总结)当处于Shutdown和Stop状态的线程池,当所有的工作线程已经销毁,任务缓存队列已经清空,或者已经执行结束,线程池将被设置 为:Terminated(终结)状态。
3.线程池中任务的执行:
3.1,再来回顾下ThreadPoolExecutor(类)中构造方法的几个重要参数:
Corepoolsize:线程池的大小,拥有的线程的数量(核心线程池);
Maximumcorepoolsize:也是线程池的大小,不过是当任务突然增大时的一种补救措施(扩大当前线程数量);
Largestpoolsize:记录量,用来记录线程池中曾经有的最大的数字。
Poolsize:线程池中的当前的线程数;
3.2ThreadPoolExecutor(类)中执行任务的execute()方法,因为submit方法底层也调用了execute(),所以只分析execute方法;
当任务提交给线程池之后的处理策略:
如果当前线程池的大小(poolsize)小于corepoolsize(核心线程池大小),则每来一个任务就会创建一个线程去执行;
如果当前线程池的大小(poolsize)>=corepoolsize(核心线程池大小),则每来一个任务就会尝试将其添加到任务缓存队列中,若添加成功,该 任务就会等待有空闲的线程来执行它;若添加失败(一般是任务缓存队列已满),就会尝试创建一个线程去执行这个任务;
如果当前线程池大小(poolsize)达到maximumpoolsize,就会采取任务拒绝策略进行处理。
(注意)如果当前线程池大于maximumpoolsize,则在某一线程,空闲时间超过KeepAliveTime后,线程将会被终止,直到线程池的数量不大于 corepoolsize;如果设置了核心池中线程的存活时间,那么线程池中的线程空闲时间超过KeepAliveTime,线程也会被终止。
4.线程池中线程的初始化:
4.1首先默认情况下,创建完线程池后,线程池中是没有线程的,当有任务来时才会创建线程。
若要求在创建完线程池后就有线程,可调用如下方法:
prestartCoreThread():初始化一个核心线程;
prestartAllCoreThreads():初始化所有的核心线程;à等待任务队列中有任务的到来。
5.任务缓存队列及排队策略:
任务缓存队列(work queue):用来存放等待执行的任务。数据类型为:BlockingQueue<Runnable>,通常可以取下面三种类型:
1) ArrayBlockingQueue:基于数组的先进先出队列;
2) LinkedBlockingQueue:基于链表的先进先出队列;
3) SynchronousQueue:该队列不会保存提交的任务,而是直接新建一个线程来执行新来的任务。
6.任务拒绝策略:
如果当前的线程数已经达到了maximumpoolsize,并任务缓存队列已满,再来任务时就会采取任务拒绝策略。(通常有以下4种策略):
1):ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2):ThreadPoolExecutor.DiscarPolicy:丢弃任务,但不抛出异常;
3):ThreadPoolExecutor.DiscarOldestPolicy:丢弃任务队列中最前面的任务,执行最新的任务;
4):ThreadPoolExecutor.CallerRunPolicy:由调用线程处理该任务。
7.线程池的关闭:
ThreadPoolExecutor类中有两种方法用于线程池的关闭:
Shutdown():调用该方法后,线程池将不再接受新的任务,等待线程池任务缓存队列执
完毕后,关闭线程池。
Shutdownnow():调用该方法后,立即关闭线程池,并尝试中断正在执行的任务,清空任务队列中的任务。,返回尚未执行的任务。
8.线程池容量的动态调整:
ThreadPoolExecutor类中提供了动态调整线程池大小的方法:
Setcorepoolsize():设置核心池的大小;
Setmaximumpoolsize():设置线程池的最大容量大小。
此处有一个使用ThreadPoolExecutor的实例(创建线程池,去执行任务)
1 public class ThreadPoolExecutorTest { 2 public static void main(String[] args) { 3 //定义线程池 4 ThreadPoolExecutor threadPoolExecutor = 5 new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, 6 new LinkedBlockingQueue<>(5)); 7 8 for (int i = 0; i < 15; i++) { 9 Mytask mytask = new Mytask(i); 10 threadPoolExecutor.execute(mytask); 11 12 System.out.println("当前线程池中的线程数:"+threadPoolExecutor.getPoolSize()+ 13 "队列中等待执行的线程数目:"+threadPoolExecutor.getQueue().size()+ 14 "已经执行完的线程的数目:"+threadPoolExecutor.getCompletedTaskCount()); 15 } 16 threadPoolExecutor.shutdown(); //关闭线程池 17 } 18 } 19 20 class Mytask implements Runnable{ 21 22 int num; 23 24 public Mytask(int num) { 25 this.num = num; 26 } 27 28 @Override 29 public void run() { 30 System.out.println("task:"+num+"正在执行任务!"); 31 try { 32 Thread.sleep(100); 33 System.out.println("task:"+num+"执行完毕!"); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 } 38 }
9.在Java doc中使用Executors类中提供的几个静态方法,来创建线程池:
Executors类直接只继承自Object。其中的几个静态方法来创建线程池:
1):Executors . newCachedThreadPool(); 创建一个缓冲池,缓冲池容量大小为:Integer .Max_VALUE;
2):Executors .newSingleThreadExecutor(): 创建容量为一的缓冲池;
3):Executors . newFixedThreadPool(int): 创建固定容量大小的缓冲池。
从这三个静态方法的源码来看,底层还是调用了ThreadPoolExecutor的构造方法,只不过参数已经配好了。
其中:
newFixedThreadPool创建的线程池corepoolsize和maximumpoolsize值是相等的,它使用的是LinkedBlockingQueue;
newSingleThreadExecutor将corepoolsize和maximumpoolsize的值都设为1,也是用LinkedBlockingQueue;
newCachedThreadPool将corepoolsize设为0,将maximumpoolsize设置为Integer.MAX_VALUE,使用的是SynchronousQueue,也就是来了任务就创建任务运行,当线程空闲60秒,就销毁线程
另见:Java Util Concurrent包中一些接口和类的使用: