一、一般创建线程池的方式主要是以下3种:
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
二、重点讲解:
1.Executors.newFixedThreadPool(3)的底层源码:
2.Executors.newCachedThreadPool();的底层源码:
3.Executors.newSingleThreadExecutor();的底层源码:
4. new LinkedBlockingQueue() 默认最大队列可以是Integer.MAX_VALUE
总结:
1、第2种线程池创建方式,存在一个问题。ThreadPoolExecutor构造方法第二参数表示最大线程数(线程池的7种参数,下面讲解)。Integer.MAX_VALUE 的大小等于2147483647,当线程池创建这么多线程时,会造成OOM。
2、第1和第3种方法默认最大是队列是 new LinkedBlockingQueue(),其默认值是Integer.MAX_VALUE,如果这时候队列堆积大量的请求,会造成OOM。
3、我们可以发现三种线程池的底层都是使用了ThreadPoolExecutor构造方法(推荐)。所以我们可以使用自定义ThreadPoolExecutor的方式创建线程池。比如:
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(3, 5,
1L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
这样我们可以自己控制线程池中最大线程池的大小和核心线程数的大小。
三、线程池的7中参数:
1.corePoolSize:线程池中的常驻核心线程数。
2.maximumPoolSize:线程池能够容纳,同时执行的最大线程数,值必须大于等于1.
3.keepAliveTime: 多余空闲线程的存活时间
4.unit: keepAliveTime的时间单位
5.workQueue:工作队列(也称阻塞队列),被提交但未被执行的任务。
工作队列有四种模式:
①ArrayBlockingQueue:是一个基于数组的有界阻塞队列,次队列按FIFO(先进先出)原则对元素进行排序。
②LinkedBlockingQuene:是一个基于链表结构的有界(默认值是Integer.MAX_VALUE)阻塞队列,次队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockQueue。
③SynchronousQuene:是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于前两种。
④PriorityBlockingQueue:支持优先级排序的无界阻塞队列
6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认即可。
7.handler:拒绝策略,表示当队列满了,并且工程线程大于等于最大线程数maximumPoolSize时,拒绝请求。
拒绝策略有四种模式:
①AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
②DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃。
③DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
④CallerRunsPolicy:由调用线程处理该任务。
四、线程池的工作原理。
如上图所以,表示核心线程数有2个,空闲线程数有3个,即总的线程数maximumPoolSize有5个,假设工作队列最大能存储3个线程。当有9个线程请求任务进来时,2个先占用核心线程数,剩下的7个想去工作队列区排队,发现只能允许3个线程排队,然后剩下4个线程进不去工作队列区排队,这时候就启动了空闲线程数3个,没有进入队列区的4个线程就抢占刚启动的3个空闲的线程,这时候核心线程,空闲线程数,工作队列区都满了,剩下一个线程就会被拒绝。。当达到多余空闲线程的存活时间
keepAliveTime时,如果线程数已经处理结束或者核心线程能够处理的情况下,空闲的线程就会被销毁。
线程池详细的处理过程:
1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2.当调用execute() 方法添加一个任务时,线程池会做如下判断:
a)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
c)如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
d)如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。
五、线程池的优点:
1.降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
3.提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。