线程池
前言
- 线程是程序执行的最小单位,是实际运作的单位。
- 没有启动的软件成为程序,整个程序启动后会产生一个进程,而一个进程中可能会有多个线程分别执行自己的任务。
- 线程池 和 数据库的连接池 等是一个概念,就是为了避免频繁创建对象,销毁对象来进行无意义的浪费资源。
- 学习文章:线程池
线程生命周期
- 第一步通过 New 来创建一个线程对象
- 创建完线程对象后 .start() 启动后并不是直接运行,而是进入就绪状态,等待CPU的调度来执行
- cpu将资源给到 就绪状态 后就会 运行线程任务,到达运行状态,使用CPU资源来执行任务
- 运行状态 之后 如果任务完美执行完毕没有被 其他 线程 抢去CPU资源,就会进入 死亡状态,等待垃圾回收
- 运行状态 后如果任务中有 .sleep() 或 .wait() 进行休眠,那么就会进入 阻塞状态 , CPU资源会被其他线程抢走。
- 阻塞状态后,就会进入就绪状态,等待CPU重新调度资源,来执行未执行完的任务。
ThreadPoolExecutor
- java提供了一个创建线程类的 接口: Executor
- ThreadPoolExecutor是实现了 Executor , 来创建线程池对象
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- 线程池运行流程
构造参数
-
corePoolSize:创建线程池的核心线程数量,该线程数不会被回收
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。 调用了prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
-
maxnumPoolSize:线程池最大可以容纳的线程数量
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没用了。
-
KeepAliveTIme:线程池中除去 核心线程外,其它线程的存活时间
线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程利用率。
-
TimeUnit: 时间单位
可选的单位有天(Days)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)
-
workQueue: 等待队列,当任务太多,线程数不够时,就会给放到队列中,等待被执行,执行FIFO原则(先进先出)
用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列: ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO原则排序 LinkedBlockingQueue:基于链表结构的阻塞队列,吞吐量高于ArrayBlockingQUeue,静态工厂方法 Excutors.newFixedThreadPool() 使用这个队列 SynchronousQueue: 不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量高于LinkedBlockingQueue,静态工厂方法Excutors.newCachedThreadPool()使用了这个队列 PriorityBlockingQueue: 一个具有优先级的无限阻塞队列。
-
threadFactory:创建线程的线程工厂,可以进行一些定制操作
可以通过线程工厂为每个创建出来的线程设置更有意义的名字,如开源框架guava
-
RejectedExecutionHandler: 拒绝策略,当任务满了,完全放不下时执行的拒绝手段
当前线程数量饱和了,采用一种策略来处理新提交的任务: AbortPolicy:直接抛出异常,默认情况下采用这种策略 CallerRunsPolicy:只用调用者所在线程来运行任务 DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务 DiscardPolicy:不处理,丢弃掉
创建代码
-
创建线程池,可以创建THreadPoolExecutor对象来创建, 也可以使用Executors 的工厂方法来创建
-
ThreadPoolExecutor:
public ThreadPoolExecutor threadPoolExecutor() { // 创建一个 队列 LinkedBlockingQueue queue = new LinkedBlockingQueue<>(10); // 核心线程4个 最大线程数8 非核心线程存活时间300 TimeUnit时间单位 Queue超过线程数存储到什么队列中 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 8, 300L, TimeUnit.SECONDS, queue); // 设置线程工程,自定义一下 threadPoolExecutor.setThreadFactory(new ThreadFactory() { @Override public Thread newThread(@NotNull Runnable r) { // ThreadFactory工厂 , 可以对 线程任务分发线程,进行一些特定行为:取名称..等 return new Thread(r , "任务class名称:---" + r.getClass().getName()); } }); // 设置内存饱满处理机制 // hreadPoolExecutor.AbortPolicy() 是一个内部类 , 所以这个new 是创建的 AbortPolicy 对象 threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 创建线程池 return threadPoolExecutor; }
-
使用Executors 静态工厂方法来创建:
CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。 SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。 SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。 FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
// 创建方法 ExecutorService executorService = Executors.newFixedThreadPool(10);
启动任务
- 启动线程任务有两种方法:
- execute方法用于提交不需要返回值的任务
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
- submit方法用于提交一个任务并带有返回值,这个方法将返回一个Future类型对象。可以通过 这个返回对象判断任务是否执行成功,并且可以通过future.get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成。
Future<?> future=threadPoolExecutor.submit(futureTask);
Object value=future.get();
关闭线程池
- 有两个关闭线程池的方法
- shutdown或shutdownNow方法 :原理都是遍历线程池中的工作线程,然后逐个调用线程的 interrup() 方法来中断线程,所以无响应中断的任务可能永远无法停止。
-
shutdownNow首先将线程池的状态设置为STOP,然后尝试立刻停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表
-
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后慢慢等任务执行完后中断所有正在执行的任务。
-
只要调用了这两个关闭方法的一个,isShutdown就会返回true。当所有的任务都关闭后,才表示线程池关闭成功,这是调用isTerminated方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定执行完,则可以调用shutdownNow方法。