Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式
前言:
在刚学Java并发的时候基本上第一个demo都会写new Thread来创建线程。但是随着学的深入之后发现基本上都是使用线程池来直接获取线程。那么为什么会有这样的情况发生呢?
new Thread和线程池的比较
每次new Thread是新建了线程对象,并且不能重复使用,为什么不能重复使用?因为new是相当于在内存中独立开辟一个内存来让该线程运行,所以只能释放线程资源和新建线程,性能差。而使用线程池,可以重复使用存在的线程,减少对象的创建,消亡的开销,性能较好
线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多的系统资源导致死机或者抛出OutOfMemoryError。而使用线程池可以有效控制最大并发线程数,提高系统资源利用率,同时避免过多资源竞争,避免阻塞
同时new Thread,当我们需要定期执行,更多执行,线程中断等等使用Thread操作起来非常的繁琐。线程池则提供定时执行,定期执行,单线程,并发控制等功能,让我们操作线程起来特别方便
ThreadPoolExecutor如何创建对象
在这里介绍的是JUC包下的ThreadPoolExecutor线程池,这个线程池里有4个构造方法
public class ThreadPoolExecutor extends AbstractExecutorService{
//第一个构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//第二个构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
//第三个构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
//第四个也是真正的初始化构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
}
从这四个函数来看,其实是分是否需要默认的线程池工厂和handler。接下来就讲讲这些参数代表什么。
corePoolSize:核心线程数量。当线程数少于corePoolSize的时候,直接创建新的线程,尽管其他线程是空闲的。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximunPoolSize:线程池最大线程数。如果线程数量少于线程最大数且大于核心线程数量的时候,只有当阻塞队列满了才创建新线程。当线程数量大于最大线程数且阻塞队列满了这时候就会执行一些策略来响应该线程。
workQueue:阻塞队列,存储等待执行的任务,会对线程池的运行产生很大的影响。当提交一个新的任务到线程池的时候,线程池会根据当前线程数量来选择不同的处理方式
直接切换队列SynchronousQueue:该队列传递任务到线程而不持有它们。在这一点上,试图向该队列压入一个任务,如果没有可用的线程立刻运行任务,那么就会入列失败,所以一个新的线程就会被创建。当处理那些内部依赖的任务集合时,这个选择可以避免锁住。直接接传递通常需要无边界的最大线程数来避免新提交任务被拒绝处理。当任务以平均快于被处理的速度提交到线程池时,它依次地确认无边界线程增长的可能性
使用无界队列LinkedBlockingQueue:使用这个队列的话,没有预先定义容量的无界队列,最大线程数是为corePoolSize,在核心线程都繁忙的时候会使新提交的任务在队列中等待被执行,所以将不会创建更多的线程,这时候,maximunPoolSize最大线程数的值将不起作用。当每个任务之间是相互独立的时比较适合该队列,任务之间不能互相影响执行。
使用有界队列ArrayBlockingQueue:使用这个队列,线程池中的最大线程数量就是maximunPoolSize,能够降低资源消耗,但是却使得线程之间调度变得更加困难,因为队列容量和线程池都规定完了。
如果想降低系统资源消耗,包括CPU使用率,操作系统资源消耗,上下文切换开销等等,可以设置一个较大的队列容量,较小的maximunPoolSize。如果线程经常发生阻塞,那么可以稍微将maximunPoolSize设置大一点
keepAliveTime:线程没有任务执行最多保持多久时间终止。也就是当线程数量超过核心线程数量的时候,且小于最大线程数量,这一部分的线程在没有任务执行的时候是会保持直到超过keepAliveTime才会销毁
unit:keepAliveTime的时间单位
threadFactory:线程工厂,用来创建线程,当使用默认的线程工厂创建线程的时候,会使得线程具有相同优先级,并且设置了守护性,同时也设置线程名称
handler:拒绝策略。当workQueue满了,并且没有空闲的线程数,即线程达到最大线程数。就会有四种不同策略来处理
直接抛出异常(默认)
使用调用者所在线程执行任务
丢弃队列中最靠前的任务,并执行当前任务
直接丢弃当前任务
这四种就是对应的策略实现类(从上到下),是在ThreadPoolExecutor中的内部类
线程池五种状态
RUNNING:在这个状态的线程池能判断接受新提交的任务,并且也能处理阻塞队列中的任务
SHUTDOWN:处于关闭的状态,该线程池不能接受新提交的任务,但是可以处理阻塞队列中已经保存的任务,在线程处于RUNNING状态,调用shutdown()方法能切换为该状态。
STOP:线程池处于该状态时既不能接受新的任务也不能处理阻塞队列中的任务,并且能中断现在线程中的任务。当线程处于RUNNING和SHUTDOWN状态,调用shutdownNow()方法就可以使线程变为该状态
TIDYING:在SHUTDOWN状态下阻塞队列为空,且线程中的工作线程数量为0就会进入该状态,当在STOP状态下时,只要线程中的工作线程数量为0就会进入该状态。
TERMINATED:在TIDYING状态下调用terminated()方法就会进入该状态。可以认为该状态是最终的终止状态。
execute()方法解析(JDK1.8)
execute():提交任务交给线程池运行
public class ThreadPoolExecutor extends AbstractExecutorService {
public void execute(Runnable command) {
//任务为空则报空异常
if (command == null)
throw new NullPointerException();
/*
* 1. 如果正在运行的线程数少于corePoolSize。那么就尝试去开始一个新线程并用传入的command作为它第一个任务,
* 然后让addworker去原子性的检查线程池的运行状态和线程数量,以至于能提前知道是否能添加线程进去。
* 如果成功则线程运行,不成功就会去到下一步,并且再获取一次ctl的值(ctl是用来获取线程状态和线程数的)
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
/*
* 2.如果一个任务能被成功的排队,那么仍然需要双重检查是否我们需要添加一个新的线程(因为有可能会第一次检查完后有一个线程销毁,所以需要双重检查)
* 或者进入此方法后线程关闭。所以很有必要重新检查一遍状态是否需要回滚排队,是否停止或开始一个新线程或是否不存在
*/
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
*3.如果我们无法将任务进行排队(即进入队列中),那么我们尝试添加一个新线程,如果失败我们就使用拒绝策略拒绝这个任务
*/
else if (!addWorker(command, false))
reject(command);
}
}
线程池的一些常用方法
submit():提交任务,能够返回执行结果execute+Future
shutdown():关闭线程池,等待任务都执行完
shutdownNow():关闭线程池,不等待任务执行完
getTaskCount():线程池已执行和未执行的任务总数
getCompletedTaskCount():已完成的任务数量
getPoolSize():线程池当前线程数量
getActiveCount():当前线程池中正在执行任务的线程数量
Executor线程池创建的四种线程
newFixedThreadPool:创建的是定长的线程池,可以控制线程最大并发数,超出的线程会在线程中等待,使用的是无界队列,核心线程数和最大线程数一样,当线程池中的线程没有任务时候立刻销毁,使用默认线程工厂。
newSingleThreadExecutor:创建的是单线程化的线程池,只会用唯一一个工作线程执行任务,可以指定按照是否是先入先出,还是优先级来执行任务。同样使用无界队列,核心线程数和最大线程数都是1个,同样keepAliveTime为0,可选择是否使用默认线程工厂。
newCachedThreadPool:设定一个可缓存的线程池,当线程池长度超过处理的需要,可以灵活回收空闲线程,如果没有可以回收的才新建线程。没有核心线程数,当线程没有任务60s之后就会回收空闲线程,使用有界队列。同样可以选择是否使用默认线程工厂。
newScheduledThreadPool:支持线程定时操作和周期性操作。
下面是方法的源码:
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
}
Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式的更多相关文章
- 线程池ThreadPoolExecutor分析: 线程池是什么时候创建线程的,队列中的任务是什么时候取出来的?
带着几个问题进入源码分析: 1. 线程池是什么时候创建线程的? 2. 任务runnable task是先放到core到maxThread之间的线程,还是先放到队列? 3. 队列中的任务是什么时候取出来 ...
- Java并发编程原理与实战五:创建线程的多种方式
一.继承Thread类 public class Demo1 extends Thread { public Demo1(String name) { super(name); } @Override ...
- 【Java EE 学习 80 下】【调用WebService服务的四种方式】【WebService中的注解】
不考虑第三方框架,如果只使用JDK提供的API,那么可以使用三种方式调用WebService服务:另外还可以使用Ajax调用WebService服务. 预备工作:开启WebService服务,使用jd ...
- Android 线程池系列教程(3) 创建线程池
Creating a Manager for Multiple Threads 上一课 下一课 1.This lesson teaches you to Define the Thread Pool ...
- 面试必备:Java线程池解析
前言 掌握线程池是后端程序员的基本要求,相信大家求职面试过程中,几乎都会被问到有关于线程池的问题.我在网上搜集了几道经典的线程池面试题,并以此为切入点,谈谈我对线程池的理解.如果有哪里理解不正确,非常 ...
- Java面试必问之线程池的创建使用、线程池的核心参数、线程池的底层工作原理
一.前言 大家在面试过程中,必不可少的问题是线程池,小编也是在面试中被问啥傻了,JUC就了解的不多.加上做系统时,很少遇到,自己也是一知半解,最近看了尚硅谷阳哥的课,恍然大悟,特写此文章记录一下!如果 ...
- Java创建线程四种方式
1.继承Thread类 public class MyThread extends Thread { public MyThread() { } public void run() { for(int ...
- Java线程池解析
Java的一大优势是能完成多线程任务,对线程的封装和调度非常好,那么它又是如何实现的呢? jdk的包下和线程相关类的类图. 从上面可以看出Java的线程池主的实现类主要有两个类ThreadPoolEx ...
- Java并发编程原理与实战三十七:线程池的原理与使用
一.简介 线程池在我们的高并发环境下,实际应用是非常多的!!适用频率非常高! 有过使用过Executors框架的朋友,可能不太知道底层的实现,这里就是讲Executors是由ThreadPoolExe ...
随机推荐
- B.大钉骑马走江湖
江湖是什么,对于在象棋界厮杀的大钉来说,江湖就是一个矩阵,他的目标,就是在江湖之中骑着马,从他的位置出发,走到终点. 当然,大钉的马也遵从中国象棋中的“马走日”的规则,而且在矩阵中,也会有一些障碍物, ...
- nginx配置实战以及查看并发数
http://www.cnblogs.com/kevingrace/p/6095027.html http://www.cnblogs.com/lianzhilei/p/6025267.html
- 蒟蒻的HNOI2017滚粗记
蒟蒻的第一次省选,然而并没有RP爆发... Day 1: 8:00开考,(然而密码错误是什么鬼).跌跌撞撞,8:40终于拿到纸质试题. { T1:作为一名没有学过Splay的蒟蒻,考场上真的被出题人感 ...
- CodeForces-245H:Queries for Number of Palindromes(3-14:区间DP||回文串)
Times:5000ms: Memory limit:262144 kB 给定字符串S(|S|<=5000),下标由1开始.然后Q个问题(Q<=1e6),对于每个问题,给定L,R,回答区间 ...
- [Selenium] Explicit wait 方法
(1) new WebDriverWait(driver, 10). until(ExpectedConditions.elementToBeClickable(locator)); (2) ne ...
- 一个NodeJS写的基于MVC的服务器
目前实现了静态文件下载.根据地址导航到控制器相应的控制器方法,但视图模版功能目前还未实现. 服务器代码(httpserver.js): var http = require("http&qu ...
- (水题)洛谷 - P1022 - 计算器的改良
https://www.luogu.org/problemnew/show/P1022 服了,居然还有+0.000和-0.000的不同,真的服了. #include<bits/stdc++.h& ...
- hdoj1260【简单DP】
这题就是一个人买还是两个人买,直接选择一下,而且默认是排好了的,就是DP一下,可能不知道DP的人,也是这么写的吧.DP是一种思想啊. #include <bits/stdc++.h> us ...
- python __builtins__ str类 (65)
65.'str', 字节转换成字符串.第一个传入参数是要转换的字节,第二个参数是按什么编码转换成字符串 class str(object) | str(object='') -> str | s ...
- CF1045G AI robots(动态开点线段树)
题意 火星上有$N$个机器人排成一行,第$i$个机器人的位置为$x_{i}$,视野为$r_{i}$,智商为$q_{i}$.我们认为第$i$个机器人可以看到的位置是$[x_{i}-r_{i},x_{i} ...