【多线程与高并发】——线程池、线程池原理、线程池五种状态、线程池的七大参数
线程池
线程
什么是线程?
线程是资源调度的最小单位,也是轻量级进程 LWP
(Light Weight Process)
创建线程的四种方式:
1、继承Thread类创建线程类
2、从线程池中获取
3、实现Runnable接口
Runnable接口不会返回结果或抛出检查异常,但是Callable接口可以
4、实现Callable接口和Future创建线程
Java线程状态变化:
线程创建之后它将处于 NEW(新建) 状态,调用 start()
方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。
什么是线程池
所谓线程池,就是一次性创建出多个线程,当一个任务到来的时候可以直接从线程池中获取一个线程来执行任务,不需要去创建;当任务执行完毕,直接将线程还给线程池,不需要销毁。这里做避免了频繁的创建和销毁带来的消耗。
线程池的核心就是生产者-消费者模型
- 生产者(调用submit()或execute()方法将任务task放入队列)
- 消费者(worker线程)循环从队列中取出任务处理任务(执行task.run())
线程池的好处
池化技术: 在线程池、数据库连接池、HTTP连接池中都有使用,主要是为了减少每次获取资源的消耗,提高对资源的利用率。
**第一:降低资源消耗。**通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
**第二:提高响应速度。**当任务到达时,任务可以不需要等到线程创建就能立即执行。
**第三:提高线程的可管理性。**线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
创建线程池
- 通过构造方法实现
- 通过Executor框架的工具类Executors来实现
- 通过
ThreadPoolExecutor
的方式
FixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
SingleThreadExecutor
创建一个单线程化的线程池,它只会唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行
CachedThreadPool
创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
ScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
线程池实现的原理
线程池的实现过程没有用到Synchronized关键字,用的都是volatile,lock和同步阻塞队列,atomic相关类,FutureTask等等,因为后者的性能更优。
线程优点:1. 线程重用;2. 控制最大并发数;3. 管理线程
1. 线程复用过程
2. 控制最大并发数
3. 管理线程
线程池的工作流程
判断核心线程是否已满——> 判断阻塞队列是否已满——>线程池是否已满——>按照策略处理无法执行功能的任务
线程池工作流程
线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
当调用 execute() 方法添加一个任务时,线程池会做如下判断:
如果正在运行的线程数量小于
corePoolSize
,那么马上创建线程运行这个任务;如果正在运行的线程数量大于或等于
corePoolSize
,那么将这个任务放入队列;如果这时候队列满了,而且正在运行的线程数量小于
maximumPoolSize
,那么还是要创建非核心线程立刻运行这个任务;如果队列满了,而且正在运行的线程数量等于
maximumPoolSize
,那么线程池会抛出异常RejectExecutionException
。当一个线程完成任务时,它会从队列中取下一个任务来执行。
当一个线程无事可做,超过一定的时间(
keepAliveTime
)时,线程池会判断,如果当前运行的线程数大于corePoolSize
,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize
的大小。
整个线程池的大体流程代码:
//有任务提交过来的话,会执行这个方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//这个是先做第一个判断当前线程是不是大于等于核心线程池(说明满了),如果大于会继续执行第二步把提交过来的任务添加到任务队列中去
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列;
if (runState == RUNNING && workQueue.offer(command)) {
//如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,
//则说明需要启用备用球员来上场(maximumPoolSize可以把这个看成是紧急预备队),来去处理这个提交的任务
if (runState != RUNNING || poolSize == 0)
//然后去处理任务
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
四种常见的线程池
newCachedThreadPool——可缓存线程池
如果线程池长度超过处理需要,可领过回收空闲线程,若无可或会,则新建线程。
特点:
- 线程的创建数量几乎没有限制。这样可灵活的往线程池中添加线程。
- 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程
- 要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class executor {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
}
newFixedThreadPool————指定线程数量
描述:
-
创建一个指定工作线程数量的线程池。
-
每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
优点:
提高程序效率和节省创建线程时所耗的开销
缺点:
线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class fixedThreadPool {
public volatile int i;
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(8000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
newSingleThreadExecutor————单线程的Executor
描述:
- 只创建唯一的线程来执行任务。保证任务按照指定顺序(FIFO,LIFO,优先级)执行。
- 如果这个线程异常结束,会有另一个取代它
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class singleThreadExecutor {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
newScheduleThreadPool——定时线程池
描述:
- 支持定时及周期性任务执行。
栗子:
- 延迟3s执行示例代码
import java.sql.Time;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class scheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("延迟5秒执行");
}
},5, TimeUnit.SECONDS);
}
}
线程池的七大参数
corePoolSize——核心线程最大数
maximumPoolSize——线程池最大线程数
keepAliveTime——空闲线程存活时间。
-
当一个非核心线程被创建,使用完归还给线程池
-
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
unit——空闲线程存活时间单位
- 这是keepAliveTime的计量单位
workQueue——等待队列
- 当线程池满了,线程就会放入这个队列中。任务调度再取出
线程池的五种工作队列:
jdk提供四种工作队列
-
ArrayBlockingQueue——基于数组的阻塞队列,按FIFO,新任务放队尾
- 有界的数组可以防止资源耗尽问题。
- 当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。
- 如果队列已经是满的,则创建一个新线程,
- 如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
-
LinkedBlockingQuene——基于链表的无界阻塞队列,按照FIFO排序
- 其实最大容量为Interger.MAX
- 由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,
- 因此使用该工作队列时,参数maxPoolSize其实是不起作用的
-
SynchronousQuene——不缓存任务的阻塞队列
- 生产者放入一个任务必须等到消费者取出这个任务。
- 也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,
- 如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略
-
PriorityBlockingQueue——具有优先级的无界阻塞队列
优先级通过参数Comparator实现。
-
DelayQueue——延迟队列
延迟队列是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
threadFactory
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
handler——拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就用到拒绝策略
jdk中提供了4中拒绝策略
-
CallerRunsPolicy——主线程自己执行该任务
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,否则直接抛弃任务。
-
AbortPolicy——抛出异常
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常
- DiscardPolicy——直接丢弃
该策略下,直接丢弃任务,什么都不做
- DiscardOldestPolicy——早删晚进
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
线程池的五种状态
线程池的五种状态:
running
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(2) 状态切换:线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,且线程池中的任务数为0
shutdown
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
stop
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
tidying
(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
terminated
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。