多线程八 线程池
发布日期:2022-04-11 08:52:53 浏览次数:2 分类:技术文章

本文共 5634 字,大约阅读时间需要 18 分钟。

文章目录

1. 线程池

创建线程的方式

  • 继承 Thread
  • 实现 Runnable
  • 实现 Callable
  • 线程池(推荐使用线程池来创建线程)

线程池概述:

程序启动一个新的线程的成本是比较高的,因为它要与操作系统来交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生命周期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程在调用结束后,并不会死亡,而是再次回到线程池中成为空闲状态,再次等待调度。在JDK5之前,线程池必须手动来创建;JDK5之后,Java内置线程池来直接使用。

线程池的三个优点如下:

  1. 降低资源消耗: 通过重复利用已创建的线程,降低线程创建和销毁带来的开销
  2. 提高响应速度: 当任务到达时,任务可以不需要等待线程的创建能立即执行(线程池中存在已经创建好的线程来直接使用)
  3. 提高线程的可管理性: 使用线程池可以统一进行线程分配、调度和监控

在这里插入图片描述
源码解析 Interface Executor
在这里插入图片描述
先来看一下线程池的简单使用

我们在线程池中创建两个线程来执行三个线程任务
在这里插入图片描述
线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
在这里插入图片描述

2. 线程池核心接口

ExecutorService 普通线程池

向线程池提交任务:

execute(Runnable command)

submit(Collable<T> task || Runnable)

ScheduledExecutoService 定时线程池

scheduleAtFixedRate()

ThreadPoolExecutor 线程池核心类

ThreadPoolExecutor : ExecutorService 的子类

ThreadPoolExecutor(int corePoolSize,intmaximumPoolSize,
   long keepAliveTime,TimeUnit unit,
   BlockingQueue  workQueue,
   RejectedExecutionHandler handler)

3. 线程池工作原理

在这里插入图片描述

[外链图片转存失败(img-4QrjGqJw-1565343912034)(C:\Users\j2726\AppData\Roaming\Typora\typora-user-images\1564904750301.png)]

线程池工作流程:

当一个任务提交给线程池时 ---->

核心线程池 corePoolSize

最大线程池 maximumPoolSize

阻塞队列 BlockingQueue

拒绝策略 RejectPolicy

  1. 首先判断核心池的线程数量是否达到 corePoolSize. 若未达到,线程池创建新的线程执行任务并将其置入核心池中; 否则,判断核心线程池是否有空闲线程. 若有,分配任务执行; 否则,进入步骤2
  2. 判断当前线程池中线程数量有没有达到线程池的最大数 maximumPoolSize. 若没有,创建新的线程执行任务并将其置入线程池中; 否则,进入步骤3
  3. 判断阻塞队列是否已满. 若未满,将任务置入阻塞队列中等待调度. 否则,进入步骤4
  4. 调用相应的拒绝策略打回任务.(有四种拒绝策略,默认抛出异常给用户 AbortPolicy)

4. java中常见的阻塞队列

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,静态工厂方法Executors.newFixedThreadPool()使用了这个队列
  • 内置线程池 FixedThreadPool, SingleThreadPool
  • SynchronousQueue:一个不存储元素的阻塞队列
  • 内置线程池 CachedThreadPool (一个元素的插入必须等待同时有一个元素的删除操作,否则插入操作就一直阻塞)
  • PriorityBlockingQueue: 基于优先级的阻塞队列

5. 创建线程池以及向线程池提交任务

手工创建一个线程池

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 3, 5,
2000,TimeUnit.MILLISECONDS,
  new LinkedBlockingDeque ());

可以使用两个方法向线程池提交任务,分别为execute() 和 submit()方法

  • execute()方法用于提交不需要返回值的任务; 所以无法判断任务是否被线程池执行成功
  • submit()方法用于提交需要返回值的任务; 线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
线程中提交方法 传入参数
execute() Runnable
submit() Runnable 或 Callable
package com.ThreadPool;import java.util.concurrent.*;/** * @Author: Mr.Q * @Date: 2019-08-05 08:05 * @Description: */class ThreadPoolTest implements Callable
   
     {

    
   private Integer tickets = 20;
@Override
public String call() throws Exception {
   for (int i = 0; i < tickets; i++) {
   if(tickets > 0)
System.out.println(Thread.currentThread().getName() +"还剩 "+tickets--+" 票...");
}
return Thread.currentThread().getName() + "票卖完了!";
}}public class ExecutorTest {
   public static void main(String[] args) {
   ExecutorService executor = new ThreadPoolExecutor(2,3,
60,TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
ThreadPoolTest threadPoolTest = new ThreadPoolTest();
for(int i = 0; i < 5; i ++) {
   executor.submit(threadPoolTest); //submit可以提交Callable,Runnable接口
}
executor.shutdown();
}}

在这里插入图片描述
提交了5次任务,但是 ThreadPool 创建了两个线程;

原因是核心线程池未满,有空闲线程;由于实现 Callable接口只做了 System.out,线程拿到任务很快执行完成;执行任务的速度快于提交任务的速度,就不在创建线程了…

线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力

6. 合理配置线程池

查看当前电脑CPU数目:

System.out.println(Runtime.getRuntime().availableProcessors());

输出的为当前电脑可执行的线程数目,目前默认一个CPU可以同时执行俩个线程,CPU数为线程数的一半

配置核心池以及最大线程池线程数量

  • CPU密集型任务:nCPU + 1
  • IO密集型任务: 2 * nCPU

CPU密集型任务 : 频繁操作CPU(大数运算)

IO密集型任务 : 频繁操作文件(读写文件)

7. JDK 内置的四大线程池 Executor

Executor 框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。通过Executor框架的工具类Executors,可以创建3种类型的 ThreadPoolExecutor

1.缓存线程池(无大小限制的线程池)newCachedThreadPool()

使用场景:适用于负载较轻的服务器,或执行很多短期的异步任务

ExecutorService executorService = Executors.newCachedThreadPool();public static ExecutorService newCachedThreadPool() {

   return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  60L, TimeUnit.SECONDS,
  new SynchronousQueue ());
}
任务提交速度 > 线程执行速度 会不断创建线程(有可能无限创建线程将内存写满)
任务提交速度 < 线程执行速度 只会创建若干线程

2.固定大小线程池newFixedThreadPool(int nThreads)

使用场景: 适用于负载较重的服务器,来满足资源分配的要求

ExecutorService executorService = Executors.newFixedThreadPool();public static ExecutorService newFixedThreadPool(int nThreads) {

   return new ThreadPoolExecutor(nThreads, nThreads,
  0L, TimeUnit.MILLISECONDS,
  new LinkedBlockingQueue ());
}

3.单线程池newSingleThreadExecutor()

使用场景: 多线程场景下需要让任务串行执行

ExecutorService executorService = Executors.newSingleThreadExecutor();public static ExecutorService newSingleThreadExecutor() {

   return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue ()));
}

4.定时调度池: newScheduledThreadPool();

使用场景:ScheduledExecutorService是java.util.concurrent并发包下的一个接口,表示调度服务

ScheduledExecutorService scheduled = Executors.newScheduledThreadPool();public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {

   return new ScheduledThreadPoolExecutor(corePoolSize);
}

定时调度池中的方法

  1. scheduled.schedule();

延迟 delay个时间单元后创建 nThreads个线程执行 command任务

ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(); scheduled.schedule();  public ScheduledFuture
    schedule(Runnable command,
   long delay, TimeUnit unit);
  1. scheduled.scheduleAtFixedRate();

延迟 delay个时间单元后每隔 period time执行一次 command任务

ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(3); scheduled.scheduleAtFixedRate();public ScheduledFuture
    scheduleAtFixedRate(Runnable command,
  long initialDelay,
  long period,
  TimeUnit unit);
  1. scheduled.scheduleWithFixedDelay();

scheduled.scheduleAtFixedRate();scheduled.scheduleWithFixedDelay();都表示每间隔一段时间定时执行任务

  • scheduleAtFixedRate是以上一次任务的开始时间为间隔的,并且当任务执行时间大于设置的间隔时间时,真正间隔的时间由任务执行时间为准
  • scheduleWithFixedDelay是以上一次任务的结束时间为间隔

中方法详解(参考)

转载地址:https://iqqcode.blog.csdn.net/article/details/98971298 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程六 线程间的通信
下一篇:多线程入门04 —— 浅谈线程通信

发表评论

最新留言

网站不错 人气很旺了 加油
[***.36.148.203]2022年06月18日 06时38分21秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

最新文章

java中的stack类和C++中的stack类的区别 2019-04-28 04:22:11
include的两种形式、CPP的搜索路径 2019-04-28 04:22:10
gzip、bzip2和tar 2019-04-28 04:22:10
which和whereis 2019-04-28 04:22:09
file 2019-04-28 04:22:09
grep 2019-04-28 04:22:09
内存泄露检测工具--VisualC++ debugger 和 CRT 库 2019-04-28 04:22:08
find 2019-04-28 04:22:08
vi常用命令汇总 2019-04-28 04:22:07
动态规划--编辑距离问题 2019-04-28 04:22:07
C/C++中的数据类型转换 2019-04-28 04:22:04
任务的定义、任务切换的原理及实现 2019-04-28 04:22:04
指针和数组分析 2019-04-28 04:22:03
Linux中目录结构 2019-04-28 04:22:02
Cortex-M3内核中的异常 2019-04-28 04:22:02
16位汇编相关寄存器 2019-04-28 04:22:02
位运算编程小技巧 2019-04-28 04:22:01
文章汇总 2019-04-28 04:22:01
数据分析系列:绘制折线图(matplotlib)2 2019-04-28 04:22:00
获取中文字符串的拼音 2019-04-28 04:21:59