本文章由公号【开发小鸽】发布!欢迎关注!!!
老规矩–妹妹镇楼:
![](https://i-blog.csdnimg.cn/blog_migrate/eac8cddaf63e8708f2618fc814be45dd.jpeg)
一. JUC线程池
(一) 优势
-
降低资源消耗,通过重用已创建的线程来降低线程创建和销毁的性能损失;
-
提高响应速度,任务无需创建线程,直接执行;
-
提高线程的可管理性,统一管理,分配,调优和监控。
(二) 线程池状态
AtomicInteger类型的变量ctl中定义了线程池中的任务数量和线程池的状态两个信息,共32位,其中高3位表示线程池状态,低29位表示线程池中的任务数量。线程池有五种状态,如下所示:
1. RUNNING
处于该状态下线程池能够接受新的任务,以及对新添加的任务进行处理。
2. SHUTDOWN
处于该状态的线程池不可以接受新的任务,但是可以对已添加的任务进行处理。通过调用shutdown()函数进入该状态。
3. STOP
处于该状态的线程池不接受新的任务,也不处理已添加的任务,直接中断正在处理的任务。通过调用shutdownNow()函数进入该状态,这个函数尽量不要使用,因为会中断正在执行的线程。
4. TIDYING
当所有的任务终止,ctl记录的任务数量为0时,线程池变为TIDYING状态。之后会执行钩子函数terminated(),该函数在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时进行相应的处理,可以重载terminated()函数。
5. TERMINATED
线程池彻底终止。
(三) 构造方法
Java的线程池支持主要是通过ThreadPoolExecutor类实现的,它的构造方法有七个参数,如下所示:
1. corePoolSize
线程池中核心线程的数量(线程池的基本大小),当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
2. maximumPoolSize
线程池中允许的最大线程数,若线程池的阻塞队列满了后,还有任务提交,只要当前的线程数小于maximumPoolSize,就会创建线程来执行任务。
3. keepAliveTime
线程的存活时间,由于线程的创建和销毁是需要代价的,在执行完任务后不会立即销毁,而是存活一段时间,默认情况下,该参数只有在线程数大于corePoolSize时才会生效,即只有大于核心线程数时才会销毁多余的线程。
4. unit
keepAliveTime的单位,TimeUnit。
5. workQueue
当任务超过了maximumPoolSize时,就会将等待的任务保存在 workQueue中,等待的任务必须实现Runnable接口,队列有以下几种:
ArrayBlockingQueue: 基于数组的有界阻塞队列,FIFO;
LinkedBlockingQueue: 基于链表的有界阻塞队列,FIFO;
PriorityBlockingQueue: 具有优先级别的阻塞队列,
SynchronousQueue: 不存储元素的阻塞队列;
6. threadFactory
用于设置创建线程的工厂,作用是提供创建线程的功能的线程工厂,通过newThread()方法提供创建线程的功能,该方法创建的线程都是非守护线程,而且线程优先级都是默认优先级。
7. handler
RejectedExecutionHandler,线程池的拒绝策略,所谓拒绝策略是指将任务添加到线程池中时,若次线程池已经饱和,且阻塞队列也已经满了,那么线程池就会选择一种拒绝策略来拒绝该任务。
线程池提供了四种拒绝策略:
AbortPolicy: 直接抛出异常,默认策略;
CallerRunsPolicy: 用调用者所在的线程来执行任务;
DiscardOlestPolicy: 丢弃阻塞队列中最靠前的任务,并执行当前任务;
DiscardPolicy: 直接丢弃任务;
当然,我们也可以实现自己的拒绝策略,如日志记录等,只要实现RejectedExecutionHandler接口即可。
(四) 四种线程池
除了使用ThreadPoolExecutor根据实际情况创建线程池以外,Executor框架也提供了三种线程池 ,都可以通过工具类Executors来创建。还有一种线程池ScheduledThreadPoolExecutor,相当于提供了“延迟”和“周期 执行”功能的ThreadPoolExecutor。
1. FixedThreadPool
(1) 特点
FixedThreadPool是复用固定数量的线程来处理一个共享的无边界队列,在创建该线程池时,指定了线程数量,corePoolSize和maximumPoolSize都设置为了该数字,则当线程池中的线程数量等于corePoolSize时,如果继续提交任务,就会被添加到阻塞队列workQueue中,而workQueue使用的是LinkedBlockingQueue,但是没有设置范围,则是Integer.MAX_VALUE范围,相当于无界队列。
(2) 定义
该线程池的定义如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
从定义中可以看出,线程的存活时间为0,因为corePoolSize和maximumPoolSize是相等的,因此不会有线程数大于corePoolSize的情况,因此也不会销毁线程。当该线程池中的线程数小于corePoolSize时,会创建新的线程来执行其他的任务;当线程数量到达了corePoolSize时,不同的任务会复用该线程池中的固定线程,如果有等待的任务则会进入阻塞队列中等待。
(3) 实例
创建固定3个线程的线程池,通过5个线程提交任务,可以看到线程池中的线程是复用的。
package thread12;
import thread.Run;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(3);
for(int i = 0 ; i < 5; ++i){
exec.execute(new Demo());
Thread.sleep(10);
}
exec.shutdown();
}
static class Demo implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i = 0 ; i < 2; ++i){
System.out.println(name + ":" + i);
}
}
}
}
2. SingleThreadExecutor
(1) 特点
只是用单个工作线程来执行一个无边界的队列。
(2) 定义
该线程池的定义如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到,corePoolSize和maximumPoolSize都设置为1,因此只有单个线程执行任务。同时线程存活时间设置为0,因为不需要没有空闲线程需要销毁。使用的阻塞队列时LinkedBlockingQueue无界的链表阻塞队列。
如果单个线程在执行任务过程中终止了,则新的线程会代替它来执行后续的线程。
3. CachedThreadPool
(1) 特点
该线程池会根据任务的需要,在线程可用时,重用之前构造好的池中线程,否则创建新的线程。
(2) 定义
该线程池的定义如下所示:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到,corePoolSize设置为0,maximumPoolSize设置为Integer.MAX_VALUE,表示能够创建无穷多的线程,只要任务提交就会加入到阻塞队列中,只要线程池中没有线程,就会创建线程来执行任务。
线程的存活时间设置60秒,即当空闲的线程等待新任务的时间超过60秒时会被终止,阻塞队列采用 SynchronousQueue,这是一个不能存储元素的阻塞队列。
因此,该线程池适合执行大量短生命周期的异步任务,不同的任务会重用之前构造的线程,同时线程池即使在长时间空闲也不会消耗任何资源,因为不论是线程还是阻塞队列中都没有存储资源。
不过,这种线程池也存在问题,如果提交任务的速度远远大于线程池的处理速度,那么线程池就会不断地创建新的线程来执行任务,这样会消耗大量的CPU和内存资源,因此使用该线程池需要控制并发的任务数。
4. ScheduledThreadPool
(1) Timer和TimerTask的问题
在实现周期线程和延迟调度时可以使用Timer和TimerTask,但是它们存在一些问题:
Timer在执行定时任务时只会创建一个线程,如果存在多个任务,且任务时间过长,超过两个任务的间隔时间会发生问题;
如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行;
Timer执行周期任务时依赖于系统时间,如果系统时间出现问题则执行上也会有问题。
(2) 实现
ScheduledThreadPoolExecutor,继承了ThreadPoolExecutor,且实现了ScheduledExecutorService接口,相当于提供了延迟和周期执行功能的ThreadPoolExecutor。它可以安排在给定的延迟时间后执行命令,或者定期执行命令,灵活性优于Timer。
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
可以看到,该线程池内部通过继承类ThreadLocalExecutor的构造方法实现的,指定了corePoolSize,maximumPoolSize设置为最大值。使用的阻塞队列变为了DelayedWorkQueue,这个队列类似于延时队列和优先级队列。在执行定时任务时,每个任务的执行时间都不同,所以该队列的任务就是按照执行时间的升序排列,执行时间距离当前时间越近的任务排在靠前的位置,保证按照执行时间执行。
(3) 实例
创建三个延迟任务,检验延迟效果。
package thread13;
import thread.Run;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPool2 {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
System.out.println("程序开始 "+ new Date());
scheduledExecutorService.schedule(new Task(), 0, TimeUnit.SECONDS);
scheduledExecutorService.schedule(new Task(), 1, TimeUnit.SECONDS);
scheduledExecutorService.schedule(new Task(), 5, TimeUnit.SECONDS);
Thread.sleep(5000);
}
static class Task implements Runnable{
@Override
public void run() {
try {
String name = Thread.currentThread().getName();
System.out.println(name + " 开始 " + new Date());
Thread.sleep(1000);
System.out.println(name+ "结束 " + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}