1. 线程的基本知识
线程是我们工作中必然会接触到的一个概念,线程是程序执行的最小单元。线程的状态有五个,分别是新建、就绪、运行、阻塞、死亡。各个状态之间的流转关系如下
线程的创建有三种方式,分别是:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
三种实现方式中,第一种实现的本质也是实现了Runnable接口,简单演示一下三种创建线程的方式。
1.1 继承Thread类
创建的方式就是继承Thread类,重写run方法,详情如下
package com.study.rabbitmq.test;
/**
* @Author alen
* @DATE 2022/6/19 8:49
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello...");
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
使用start()方法来启动线程,让线程进入就绪状态,等待CPU资源,然后执行。
1.2 实现Runnable接口
创建方式是实现Runnable接口,重写run方法,详情代码如下
package com.study.rabbitmq.test;
/**
* @Author alen
* @DATE 2022/6/19 8:58
*/
public class MyRunnableTest implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":hello....");
}
public static void main(String[] args) {
MyRunnableTest t1 = new MyRunnableTest();
new Thread(t1, "自定义线程名称").start();
}
}
1.3 实现Callable接口
实现Callable接口,重写call方法,详情如下
package com.study.rabbitmq.test;
import java.util.concurrent.*;
/**
* @Author alen
* @DATE 2022/6/19 9:04
*/
public class MyCallableTest implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("信息:hello...");
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallableTest t1 = new MyCallableTest();
//线程池的创建
ExecutorService executor = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> submit = executor.submit(t1);
//获取执行结果
Boolean flag = submit.get();
System.out.println("flag:" + flag);
}
}
这种方式的使用,本质上是线程池的使用。线程池的使用在我们工作中也比较常见,典型的就是各类数据库的连接都是使用了线程池。线程池可以理解为是线程提前创建好,放在一个次子里面,要用的时候,就把线程拿出来使用。
2. 线程池ThreadPoolExecutor
由于线程的创建是比较消耗资源的,在高并发的请求下,频繁的创建和销毁线程对于资源来说是非常浪费的。也会极大的降低资源的处理能力,所以需要使用线程池。线程创建后,放入线程池。然后线程不断复用,达到提升处理效率。
2.1 线程池ThreadPoolExecutor的创建
在学习线程池ThreadPoolExecutor时,对于其几个核心的参数一定需要先了解清楚。参数的解释如下,ThreadPoolExecutor的构造函数源码如下:
/**
* ThreadPoolExecutor构造函数
*
* @param corePoolSize 核心线程数。默认情况下,核心线程会一直存活,但将allowCoreThreadTimeout设置为true时,核心线程也会超时回收
* @param maximumPoolSize 线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞
* @param keepAliveTime 线程闲置超时时长。如果超过该时长,非核心线程就会被回收
* @param unit 指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)
* @param workQueue 任务队列,当线程处理不过来时,任务暂时存放队列中
* @param threadFactory 线程工厂。用于指定为线程池创建新线程的方式
* @param handler 拒绝策略。当达到最大线程数时需要执行的饱和策略
*/
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;
}
其中拒绝策略有很多种,具体如下
//丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.AbortPolicy;
//也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardPolicy;
//丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.DiscardOldestPolicy;
//由调用线程处理该任务
ThreadPoolExecutor.CallerRunsPolicy;
使用代码如下
package com.study.rabbitmq.test.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Author alen
* @DATE 2022/6/19 9:27
*/
public class ThreadPoolExecutorTest {
//核心线程数
private static final Integer CORE_POOL_SIZE = 20;
//最大线程数
private static final Integer MAXIMUM_POOL_SIZE = 20;
//线程空闲超时时长
private static final Long KEEP_ALIVE_TIME = 120L;
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.HOURS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 100; i++) {
poolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + ":hello.....");
});
}
}
}
线程池中有五种常见的创建方式,分别是
- Executors.newCachedThreadPool():可缓存线程池
- Executors.newFixedThreadPool(10):固定大小的线程池
- Executors.newScheduledThreadPool(2):任务调度的线程池
- Executors.newSingleThreadExecutor():单线程的线程池
- Executors.newWorkStealingPool():足够大小的线程池
这些线程池的创建底层都是通过创建ThreadPoolExecutor对象,不同类型的线程池的参数不同而已。
3. 线程池的原理
线程池ThreadPoolExecutor对象中最核心的方法execute(),所以我们得理解这个方法的逻辑。源码如下
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
*分三步进行:
*
* 1. 如果运行的线程少于corePoolSize,请尝试
* 以给定命令作为第一个线程启动新线程
* 任务。对addWorker的调用以原子方式检查运行状态和
* workerCount,从而防止错误警报
* 在不应该的情况下,通过返回false来执行线程。
*
* 2. 如果任务可以成功排队,那么我们仍然需要
* 再次检查是否应该添加线程
*(因为自上次检查以来,现有的已死亡)或
* 自进入此方法以来,池已关闭。所以我们
* 重新检查状态,如有必要,在以下情况下回滚排队
* 已停止,如果没有线程,则启动新线程。
* 3. 如果无法将任务排队,则尝试添加新的
* 螺纹。如果失败了,我们知道我们已经被关闭或饱和了
* 所以拒绝这个任务。
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
更详细的源码分析,后续使用新的博客来分析。
4. Spring Boot中使用多线程
4.1 添加配置类
新建配置类,在配置类中配置线程池的相关配置,配置详情如下
package com.test.executor.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @Author alen
* @DATE 2022/3/8 13:48
* 线程池配置类
*/
@Configuration
//开启线程池
@EnableAsync
public class TaskExecutorConfig {
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(20);
// 队列大小
executor.setQueueCapacity(100);
// 线程空闲超时时间
executor.setKeepAliveSeconds(30);
// 是否允许超时 true:允许 false:不允许
executor.setAllowCoreThreadTimeOut(true);
// 线程名字前缀
executor.setThreadNamePrefix("thread-task-pool-");
// 拒绝策列 AbortPolicy:队列满后新进的任务直接拒绝并抛出异常
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 设置关闭线程池时,需要等待线程执行完成后方可关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
// 初始化
executor.initialize();
return executor;
}
}
4.2 线程池业务中使用
创建一个业务处理类,类中的业务处理方法标识为异步并且指定使用的线程池,详情如下
package com.test.executor.service.impl;
import com.test.executor.service.ExecutorService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @Author alen
* @DATE 2022/3/8 14:44
*/
@Service
public class ExecutorServiceImpl implements ExecutorService {
/**
* 此逻辑将使用线程池处理
*/
@Async("threadPoolTaskExecutor")
public void seat() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-----线程池开始处理任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
业务处理在逻辑中的使用如下
package com.test.executor.controller;
import com.test.executor.service.ExecutorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author alen
* @DATE 2022/3/8 14:29
*/
@RestController
@RequestMapping("/test")
public class ExecutorController {
@Autowired
private ExecutorService executorService;
@GetMapping("/seat")
public String test() {
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
executorService.seat();
}
long end = System.currentTimeMillis();
return "任务执行中。。。" + (end - start) + "ms";
}
}
可以自行测试一下。线程池的基础知识就先了解到这。