从Java线程池的常⽤4种写法深⼊分析线程池的实现原理
什么是线程池
在Java中,创建⼀个线程可以通过继承Thread或者实现Runnable接⼝来实现,但是,如果每个请求都创建⼀个新线程,那么创建和销毁线程花费的时间和消耗的系统资源都相当⼤,甚⾄可能要⽐在处理实际的⽤户请求的时间和资源要多的多。
为了解决这个问题,就有了线程池的概念,线程池的核⼼逻辑是提前创建好若⼲个线程放在⼀个容器种。如果有任务需要处理,则将任务直接分配给线程池中的线程来执⾏就⾏,任务处理完以后这个线程不会被销毁,⽽是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建⼤量线程增加开销。
创建线程池
为了⽅便使⽤,Java中的Executors类⾥⾯提供了⼏个线程池的⼯⼚⽅法,可以直接利⽤提供的⽅法创建不同类型的线程池:
newFixedThreadPool:创建⼀个固定线程数的线程池
newSingleThreadExecutor:创建只有1个线程的线程池
newCachedThreadPool:返回⼀个可根据实际情况调整线程个数的线程池,不限制最⼤线程 数量,若⽤空闲的线程则执⾏任务,若⽆任务则不创建线程。并且每⼀个空闲线程会在60秒 后⾃动回收。
newScheduledThreadPool: 创建⼀个可以指定线程的数量的线程池,但是这个线程池还带有 延迟和周期性执⾏任务的功能,类似定时器。
FixedThreadPool
创建⼀个固定数量N个线程在⼀个共享的⽆边界队列上操作的线程池。在任何时候,最多N个线程被激活处理任务。如果所有线程都在活动状态时⼜有新的任务被提交,那么新提交的任务会加⼊队列等待直到有线程可⽤为⽌。
如果有任何线程在shutdown前因为失败⽽被终⽌,那么当有新的任务需要执⾏时会产⽣⼀个新的线程,新的线程将会⼀直存在线程池中,直到被显⽰的shutdown。
⽰例
package urrent.threadPool;
import urrent.ExecutorService;
import urrent.Executors;
p ublic class TestThreadPool {
public static void main(String[] args) {
//FixedThreadPool - 固定线程数
ExecutorService fixedThreadPool = wFixedThreadPool(3);
for (int i=0;i<10;i++){
System.out.println("线程名:" + Thread.currentThread().getName());
});
}
fixedThreadPool.shutdown();
}
}
输出结果为:
可以看到,最多只有3个线程在循环执⾏任务(运⾏结果是不⼀定的,但是最多只会有3个线程)。
FixedThreadPool调⽤了如下⽅法构造线程池:
SingleThreadExecutor
只有⼀个⼯作线程的执⾏器。如果这个线程在正常关闭前因为执⾏失败⽽被关闭,那么就会重新创建⼀个新的线程加⼊执⾏器。这种执⾏器可以保证所有的任务按顺序执⾏,并且在任何给定的时间内,确保活动的任务只有1个。
⽰例
package urrent.threadPool;
i mport urrent.ExecutorService;
import urrent.Executors;
p ublic class TestThreadPool {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = wSingleThreadExecutor(); for (int i=0;i<9;i++){java线程池创建的四种
}); } }}singleThreadExecutor.shutdown();
运⾏结果只有1个线程:
SingleThreadExecutor调⽤了如下⽅法构造线程池:
CachedThreadPool
⼀个在需要处理任务时才会创建线程的线程池,如果⼀个线程处理完任务了还没有被回收,那么线程可以被重复使⽤。
当我们调⽤execute⽅法时,如果之前创建的线程有空闲可⽤的,则会复⽤之前创建好的线程,否则就会创建新的线程加⼊到线程池中。
创建好的线程如果在60s内没被使⽤,那么线程就会被终⽌并移出缓存。因此,这种线程池可以保持长时间空闲状态⽽不会消耗任何资源。⽰例
package urrent.threadPool;
i mport urrent.ExecutorService;
import urrent.Executors;
p ublic class TestThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = wCachedThreadPool(); for (int i=0;i<9;i++){
}); } cachedThreadPool.shutdown();}
输出结果可以看到,创建了9个不同的线程:
接下来我们对上⾯的⽰例改造⼀下,在执⾏execute之前休眠⼀段时间:
package urrent.threadPool;
i mport urrent.ExecutorService;
import urrent.Executors;
p ublic class TestThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = wCachedThreadPool(); for (int i=0;i<9;i++){
try {
Thread.sleep(i * 10L);
} catch (InterruptedException e) {
e.printStackTrace(); } ute(()-> { System.out.println("线程名:" + Thread.currentThread().getName( }); } cachedThreadPool.shutdown();}
这时候输出的结果就只有1个线程了,因为有部分线程可以被复⽤:
注意:这两个⽰例的结果都不是固定的,第⼀种有可能也不会创建9个线程,第⼆种也有可能不⽌创建1个线程,具体要看线程的执⾏情
况。
CachedThreadPool调⽤了如下⽅法构造线程池
ScheduledThreadPool
创建⼀个线程池,它可以在调度命令给定的延迟后运⾏或定期执⾏。这个相⽐较于其他的线程池,其⾃定义了⼀个⼦类ScheduledExecutorService继承了ExecutorService。
⽰例
package urrent.threadPool;
i mport urrent.ExecutorService;
import urrent.ScheduledExecutorService;
import urrent.Executors;
p ublic class TestThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = wScheduledThreadPool(3);
for (int i=0;i<9;i++){
}); } scheduledThreadPool.shutdown(); }}
输出结果(执⾏结果具有随机性,最多只有3个线程执⾏):
ScheduledThreadPool最终调⽤了如下⽅法构造线程池
线程池原理
根据上⾯的截图可以看到,列举的4中常⽤的线程池在构造时,最终调⽤的⽅法都是ThreadPoolExecutor类的构造⽅法,所以要分析原理,我们就去看看ThreadPoolExecutor吧!
构造线程池7⼤参数
下⾯就是ThreadPoolExecutor类中最完整的⼀个构造⽅法:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论