目录
Executors.newFixedThreadPool(int)
Executors.newSingleThreadExecutor()
Executors.newCachedThreadPool();
阻塞队列
阻塞队列是一个队列,在数据结构中起的作用如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/35254436483a7fb4ee1d0a800c60bad4.png)
当队列是空的,从队列中 获取 元素的操作将会被阻塞。当队列是满的,从队列中 添加 元素的操作将会被阻塞。试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增。阻塞队列的用处:在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。为什么需要 BlockingQueue ?好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这BlockingQueue 都给你一手包办了。在 concurrent 包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
接口架构图
ArrayBlockingQueue :由数组结构组成的有界阻塞队列。LinkedBlockingQueue :由链表结构组成的有界(默认值为: integer.MAX_VALUE )阻塞队 列。PriorityBlockingQueue :支持优先级排序的无界阻塞队列DelayQueue :使用优先级队列实现的延迟无界阻塞队列。SynchronousQueue :不存储元素的阻塞队列,也即单个元素的队列。LinkedTransferQueue :由链表组成的无界阻塞队列。LinkedBlockingDeque :由链表组成的双向阻塞队列。
API 的使用
尽量按组匹配使用
解释:
![](https://i-blog.csdnimg.cn/blog_migrate/e135af10b0d77548f6d286aa509c83a2.png)
代码测试
一直阻塞
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d");
System.out.println(blockingQueue.take()); // a
System.out.println(blockingQueue.take()); // b
System.out.println(blockingQueue.take()); // c
System.out.println(blockingQueue.take()); // 阻塞不停止等待
}
}
超时退出
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d",3L,TimeUnit.SECONDS); // 等待3秒超时退出
System.out.println(blockingQueue.poll()); // a
System.out.println(blockingQueue.poll()); // b
System.out.println(blockingQueue.poll()); // c
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS)); // 阻塞不停止等待
}
}
SynchronousQueue 同步队列
SynchronousQueue 没有容量。
与其他的
BlockingQueue
不同,
SynchronousQueue
是一个不存储元素的
BlockingQueue
。
每一个
put
操作必须要等待一个
take
操作,否则不能继续添加元素,反之亦然。
import jdk.nashorn.internal.ir.Block;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + " put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + " put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
线程池的三大方法
Java
中的线程池是通过
Executor
框架实现的,该框架中用到了
Executor
,
Executors
,
ExecutorService
,
ThreadPoolExecutor
这几个类。
![](https://i-blog.csdnimg.cn/blog_migrate/96d38730c53169ca6ecb6c9b0993f1b2.png)
三大方法说明
Executors.newFixedThreadPool(int)
执行长期任务性能好,创建一个线程池,一池有
N
个固定的线程,有固定线程数的线程。
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 池子大小 5
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
// 模拟有10个顾客过来银行办理业务,池子中只有5个工作人员受理业务
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
Executors.newSingleThreadExecutor()
只有一个线程
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 有且只有一个固定的线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
// 模拟有10个顾客过来银行办理业务,池子中只有1个工作人员受理业务
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
Executors.newCachedThreadPool();
执行很多短期异步任务,线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。
可扩容,遇强则强
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 一池N线程,可扩容伸缩
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
// 模拟有10个顾客过来银行办理业务,池子中只有N个工作人员受理业务
for (int i = 1; i <= 10; i++) {
// 模拟延时看效果
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
但这三个创建线程池的方法都不用:
只能使用自定义的
![](https://i-blog.csdnimg.cn/blog_migrate/20ed8cba4a4c968acb62c6011d529032.png)
ThreadPoolExecutor 七大参数(使用)
操作:查看三大方法的底层源码,发现本质都是调用了
new ThreadPoolExecutor ( 7 大参数 )
// 源码
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.acc=System.getSecurityManager()==null?
null:
AccessController.getContext();
this.corePoolSize=corePoolSize;
this.maximumPoolSize=maximumPoolSize;
this.workQueue=workQueue;
this.keepAliveTime=unit.toNanos(keepAliveTime);
this.threadFactory=threadFactory;
this.handler=handler;
}
参数理解:
corePoolSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize 后,就会把到达的任务放到缓存队列当中。maximumPoolSize :最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于 1 。keepAliveTime :空闲的线程保留的时间。TimeUnit :空闲线程的保留时间单位。TimeUnit . DAYS ; // 天TimeUnit . HOURS ; // 小时TimeUnit . MINUTES ; // 分钟TimeUnit . SECONDS ; // 秒TimeUnit . MILLISECONDS ; // 毫秒TimeUnit . MICROSECONDS ; // 微妙TimeUnit . NANOSECONDS ; // 纳秒BlockingQueue< Runnable> :阻塞队列,存储等待执行的任务。参数有 ArrayBlockingQueue 、 LinkedBlockingQueue、 SynchronousQueue 可选。ThreadFactory :线程工厂,用来创建线程,一般默认即可RejectedExecutionHandler :队列已满,而且任务量大于最大线程的异常处理策略。有以下取值:ThreadPoolExecutor . AbortPolicy : 丢弃任务并抛出 RejectedExecutionException 异常。ThreadPoolExecutor . DiscardPolicy :也是丢弃任务,但是不抛出异常。ThreadPoolExecutor . DiscardOldestPolicy :丢弃队列最前面的任务,然后重新尝试执 行任务 (重复此过程)ThreadPoolExecutor . CallerRunsPolicy :由调用线程处理该任务
ThreadPoolExecutor 底层工作原理即实现
1. 在创建了线程池后,开始等待请求。2. 当调用 execute() 方法添加一个请求任务时,线程池会做出如下判断:1. 如果正在运行的线程数量小于 corePoolSize ,那么马上创建线程运行这个任务:2. 如果正在运行的线程数量大于或等于 corePoolSize ,那么将这个任务放入队列:3. 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize ,那么还是 要创建非核心线程立刻运行这个任务;4. 如果队列满了且正在运行的线程数量大于或等于 1Size ,那么线程池会启动饱和拒绝 策略来执 行。3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。4. 当一个线程无事可做超过一定的时间 (keepA1iveTime) 时,线程会判断:如果当前运行的线程数大于coreP 佣 1Size ,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
例如:corePoolSize=2 核心线程数2new LinkedBlockingDeque<>(3) 队列3maximumPoolSize = 8 最大线程数Executors.defaultThreadFactory()new ThreadPoolExecutor.DiscardPolicy())刚开始:1~2 个任务立即被受理(核心大小 core )3~5个任务 2个任务立即执行 剩下的任务进入队列( Queue )6~8人任务 2个任务立即执行 3个任务进入队列 剩下的开启新线程进行执行(从第3个任务开始先进入的任务先进入队列)9个任务以上 大于8的任务直接丢弃且不抛异常
代码:
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 获得CPU的内核数
System.out.println(Runtime.getRuntime().availableProcessors());
// 自定义 ThreadPoolExecutor
ExecutorService threadPool = new ThreadPoolExecutor(
2,//核心线程数
5,//最大线程数
2L,//空闲线程保留时间
TimeUnit.SECONDS,//保留时间单位
new LinkedBlockingDeque<>(3),//队列
Executors.defaultThreadFactory(),//线程创建工厂
new ThreadPoolExecutor.DiscardPolicy());//超过最大线程数拒绝策略
try {
// 模拟有6,7,8,9,10个顾客过来银行办理业务,观察结果情况
// 最大容量为:maximumPoolSize + workQueue = 最大容量数
for (int i = 1; i <= 19; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
线程是否越多越好?
一个计算为主的程序(专业一点称为 CPU 密集型程序 )。多线程跑的时候,可以充分利用起所有的 cpu核心,比如说4 个核心的 cpu, 开 4 个线程的时候,可以同时跑 4 个线程的运算任务,此时是最大效率。但是如果线程远远超出cpu 核心数量 反而会使得任务效率下降,因为频繁的切换线程也是要消耗时间的。因此对于cpu 密集型的任务来说,线程数等于cpu数是最好的了。如果是一个磁盘或网络为主的程序( IO 密集型 )。一个线程处在 IO 等待的时候,另一个线程还可以在CPU里面跑,有时候 CPU 闲着没事干,所有的线程都在等着 IO ,这时候他们就是同时的了,而单线程的话此时还是在一个一个等待的。我们都知道IO 的速度比起 CPU 来是慢到令人发指的。所以开多线程,比方说多线程网络传输,多线程往不同的目录写文件,等等。此时线程数等于 IO 任务数是最佳的。
常用辅助类:
CountDownLatch 计数减法
CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞其他线程调用 CountDown 方法会将计数器减 1 (调用 CountDown 方法的线程不会阻塞)当计数器变为 0 时, await 方法阻塞的线程会被唤醒,继续执行
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "Start");
countDownLatch.countDown(); // 计数器-1
}, String.valueOf(i)).start();
}
//阻塞等待计数器归零
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " End");
}
/**
* 顺序不一定,结果诡异,达不到预期的最后End
*/
public void test1() {
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "Start");
}, String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName() + " End");
}
}
CyclicBarrier 计数加法
作用:和上面的减法相反,这里是加法,好比集齐
7
个龙珠召唤神龙,或者人到齐了再开会!
public class CyclicBarrierDemo {
public static void main(String[] args) {
// CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集了 第"+ tempInt +" 颗龙珠");
try {
cyclicBarrier.await(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore 信号量,信号灯,信号; 作用:抢车位
在信号量上我们定义两种操作:acquire(获取)当一个线程调用 acquire 操作时,他要么通过成功获取信号量(信号量 -1 )要么一直等下去,直到有线程释放信号量,或超时release (释放)实际上会将信号量的值 + 1,然后唤醒等待的线程。(release)信号量主要用于两个目的:一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
public class SemaphoreDemo {
public static void main(String[] args) {
// 模拟资源类,有3个空车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) { // 模拟6个车
new Thread(()->{
try {
semaphore.acquire(); // acquire 得到
System.out.println(Thread.currentThread().getName()+" 抢到了车位");
TimeUnit.SECONDS.sleep(3); // 停3秒钟
System.out.println(Thread.currentThread().getName()+" 离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放这个位置
}
},String.valueOf(i)).start();
}
}
}
利用CountDownLatch和线程池组合案列
资源类
package com.blockchain.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @BelongsProject: chaincto_app_backend
* @BelongsPackage: com.blockchain.config
* @Author: KaySen
* @CreateTime: 2022-06-08 15:24
* @Description: 线程池配置
* @Version: 1.0
*/
@Configuration
public class ThreadPoolConfig {
// 核心线程池大小
private int corePoolSize = 5;
// 最大可创建的线程数
private int maxPoolSize = 15;
// 队列最大长度
private int queueCapacity = 200;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
调用类
package com.blockchain.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blockchain.entity.*;
import com.blockchain.mapper.JobVacancyDao;
import com.blockchain.model.PageModel;
import com.blockchain.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
/**
* (JobVacancy)表服务实现类
*
* @author kaysen
* @since 2022-07-10 17:32:07
*/
@Service("JobTestService")
public class JobVacancyServiceImpl extends ServiceImpl<JobVacancyDao, JobVacancy> implements JobVacancyService {
@Autowired
private MemberService memberService;
@Autowired
private CompanyService companyService;
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Override
public List<JobVacancy> likeByJobName(String name, PageModel pageModel) {
QueryWrapper<JobVacancy> jobQueryWrapper = new QueryWrapper<>();
jobQueryWrapper.lambda().eq(JobVacancy::getStatus, 2)
.eq(JobVacancy::getIsDelete, 1)
.eq(JobVacancy::getPutOff, 1)
.like(JobVacancy::getName, name);
List<JobVacancy> list = this.list(jobQueryWrapper);
getJobVacancyDetail(list);
return list;
}
@Override
public void getJobVacancyDetail(List<JobVacancy> list) {
List<Integer> companyIds = new ArrayList<>();
List<Integer> creatorIds = new ArrayList<>();
for (JobVacancy jobVacancy : list) {
if (!ObjectUtils.isEmpty(companyIds)) {
if (!companyIds.contains(jobVacancy.getCompanyId())) {
companyIds.add(jobVacancy.getCompanyId());
}
} else {
companyIds.add(jobVacancy.getCompanyId());
}
if (!ObjectUtils.isEmpty(creatorIds)) {
if (!creatorIds.contains(jobVacancy.getCreator())) {
creatorIds.add(jobVacancy.getCreator());
}
} else {
creatorIds.add(jobVacancy.getCreator());
}
}
Map<Integer, Company> companyMap = new HashMap<>();
CountDownLatch latch = new CountDownLatch(2);//减法计数器 相当于开启两个线程
final String address="dddd";
threadPoolTaskExecutor.execute(() -> {
try {
List<Company> companies = companyService.listByIds(companyIds);
Map<Integer, Company> collect = companies.stream().collect(Collectors.toMap(Company::getId, p -> p));
System.out.println(address);//局部参数传入线程需要用final修饰 避免使用时被修改
companyMap.putAll(collect);//返回数据可以用map来承接 但hashMap有不安全的问题可用concurrentHashMap类,请看JUC-03
} finally {
latch.countDown();//计数器自减1
}
});
Map<Integer, Member> memberMap = new HashMap<>();
threadPoolTaskExecutor.execute(() -> {
try {
List<Member> members = memberService.listByIds(creatorIds);
Map<Integer, Member> collect = members.stream().collect(Collectors.toMap(Member::getId, p -> p));
memberMap.putAll(collect);
} finally {
latch.countDown();//计数器自减1
}
});
try {
latch.await();//等计数器归零才执行一下方法
} catch (InterruptedException e) {
log.error("查询职位详情异常");
}
//组装数据
for (JobVacancy jobVacancy : list) {
jobVacancy.setCompany(companyMap.get(jobVacancy.getCompanyId()));
jobVacancy.setCreatorMember(memberMap.get(jobVacancy.getCreator()));
}
}
}