多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能,我们今天就来聊聊我们工作中实现多线程的基本方式,本文中Spring Boot 版本为2.5.2,JDK环境为 1.8,本文中使用到的依赖如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.5.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.14version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>compilescope>
dependency>
继承Thread类,然后重写run()方法即可
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyThread extends Thread {
private String threadName;
public MyThread(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
log.info("继承Thread类,子线程【{}】执行,优先级【{}】", this.threadName, Thread.currentThread().getPriority());
}
}
我们写个测试方法
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
@Slf4j
public class MyThreadTest {
@Test
public void testMyThread() {
for (int i = 0; i < 3; i++) {
MyThread myThread = new MyThread("MyThread-" + i);
myThread.start();
}
log.info("继承Thread类,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
}
}
运行结果:
19:51:08.249 [Thread-1] INFO com.alian.csdn.thread.MyThread - 继承Thread类,子线程【MyThread-1】执行,优先级【5】
19:51:08.249 [main] INFO com.alian.csdn.thread.MyThreadTest - 继承Thread类,主线程【main】执行,优先级【5】
19:51:08.249 [Thread-0] INFO com.alian.csdn.thread.MyThread - 继承Thread类,子线程【MyThread-0】执行,优先级【5】
19:51:08.249 [Thread-2] INFO com.alian.csdn.thread.MyThread - 继承Thread类,子线程【MyThread-2】执行,优先级【5】
这里需要提一下几个点:
实现Runnable接口,同样需要重写run()方法
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyRunnable implements Runnable {
private String threadName;
public MyRunnable(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
log.info("实现Runnable接口,子线程【{}】执行,优先级【{}】", this.threadName, Thread.currentThread().getPriority());
}
}
我们写个测试方法
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
@Slf4j
public class MyRunnableTest {
@Test
public void testMyRunnable() {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new MyRunnable("MyRunnable-" + i));
thread.start();
}
log.info("实现Runnable接口,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
}
}
运行结果:
20:00:05.405 [Thread-2] INFO com.alian.csdn.thread.MyRunnable - 实现Runnable接口,子线程【MyRunnable-2】执行,优先级【5】
20:00:05.405 [Thread-1] INFO com.alian.csdn.thread.MyRunnable - 实现Runnable接口,子线程【MyRunnable-1】执行,优先级【5】
20:00:05.405 [Thread-0] INFO com.alian.csdn.thread.MyRunnable - 实现Runnable接口,子线程【MyRunnable-0】执行,优先级【5】
20:00:05.405 [main] INFO com.alian.csdn.thread.MyRunnableTest - 实现Runnable接口,主线程【main】执行,优先级【5】
实现Callable接口,重写V call()方法,其中 V 为返回值
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
@Slf4j
public class MyCallable implements Callable<String> {
private String threadName;
public MyCallable(String threadName) {
this.threadName = threadName;
}
@Override
public String call() throws Exception {
log.info("实现Callable接口,子线程【{}】执行,优先级【{}】", this.threadName, Thread.currentThread().getPriority());
return this.threadName + "返回结果:" + (int) (Math.random() * 100);
}
}
我们先看一个图,从下面这个图我们知道: FutureTask 也是 Runnable的一个实现。
按照之前的逻辑,我们像下面这样写个测试方法( 错误示例 )
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.ArrayList;
import java.util.concurrent.FutureTask;
@Slf4j
public class MyCallableTest {
@Test
public void testMyCallableLikeSync() throws Exception {
for (int i = 0; i < 3; i++) {
String threadName = "myCallable-" + i;
MyCallable callable = new MyCallable(threadName);
//创建FutureTask对象
FutureTask<String> task = new FutureTask<>(callable);
//启动线程
new Thread(task).start();
//获取任务结果
String result = task.get();
log.info("result={}", result);
}
log.info("实现Callable接口,变成了同步,主线程【{}】执行,优先级【{}】" , Thread.currentThread().getName(), Thread.currentThread().getPriority());
}
}
运行结果:
20:29:36.044 [Thread-0] INFO com.alian.csdn.thread.MyCallable - 实现Callable接口,子线程【myCallable-0】执行,优先级【5】
20:29:36.048 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-0返回结果:3
20:29:36.049 [Thread-1] INFO com.alian.csdn.thread.MyCallable - 实现Callable接口,子线程【myCallable-1】执行,优先级【5】
20:29:36.049 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-1返回结果:97
20:29:36.049 [Thread-2] INFO com.alian.csdn.thread.MyCallable - 实现Callable接口,子线程【myCallable-2】执行,优先级【5】
20:29:36.049 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-2返回结果:85
20:29:36.049 [main] INFO com.alian.csdn.thread.MyCallableTest - 实现Callable接口,变成了同步,主线程【main】执行,优先级【5】
我们之前继承Thread类和实现Runnable接口执行上都是多线程,但是这个不一样,从上面的结果可以看到,我们的线程看起来好像是同步的(一个线程执行完,下一个线程才能执行),这是因为获取任务的结果阻塞了,直到拿到结果,下一个线程才能创建执行,并不是我们想象的多线程。正确的方式如下:
@Test
public void testMyCallable() throws Exception {
//新建一个列表存FutureTask任务
ArrayList<FutureTask<String>> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
String threadName = "myCallable-" + i;
MyCallable callable = new MyCallable(threadName);
//创建FutureTask对象
FutureTask<String> task = new FutureTask<>(callable);
//启动线程
new Thread(task).start();
//加入任务列表
list.add(task);
}
//遍历任务,得到结果
for (FutureTask<String> task : list) {
//获取任务结果
String result = task.get();
log.info("result={}", result);
}
log.info("实现Callable接口,主线程【{}】执行,优先级【{}】" , Thread.currentThread().getName(), Thread.currentThread().getPriority());
}
运行结果:
20:32:36.216 [Thread-0] INFO com.alian.csdn.thread.MyCallable - 实现Callable接口,子线程【myCallable-0】执行,优先级【5】
20:32:36.216 [Thread-2] INFO com.alian.csdn.thread.MyCallable - 实现Callable接口,子线程【myCallable-2】执行,优先级【5】
20:32:36.216 [Thread-1] INFO com.alian.csdn.thread.MyCallable - 实现Callable接口,子线程【myCallable-1】执行,优先级【5】
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-0返回结果:57
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-1返回结果:89
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-2返回结果:81
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - 实现Callable接口,主线程【main】执行,优先级【5】
从这里我们我们可以知道,我们的线程是多线程了,只是有一点,就是在我们获取结果的时候,遍历的正好是我们没有执行的那个任务,其他的可能执行完了,但是任务是执行完了,最终的结果也能到,但是并不一定就是实时拿到。
前面我们都是创建了一个响应的类来创建线程,实际我们还可以更简单的实现,比如匿名内部类。
@Test
public void testMyRunnableWithInnerClass(){
for (int i = 0; i < 3; i++) {
String threadName="MyRunnable-" + i;
new Thread(new Runnable() {
@Override
public void run() {
log.info("通过匿名内部类实现,子线程【{}】执行,优先级【{}】", threadName, Thread.currentThread().getPriority());
}
}).start();
}
log.info("通过匿名内部类实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
}
运行结果:
20:40:58.517 [main] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,主线程【main】执行,优先级【5】
20:40:58.517 [Thread-0] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,子线程【MyRunnable-0】执行,优先级【5】
20:40:58.517 [Thread-2] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,子线程【MyRunnable-2】执行,优先级【5】
20:40:58.517 [Thread-1] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,子线程【MyRunnable-1】执行,优先级【5】
实际上,上面的方法用 Lambda 表达式可以更简单
@Test
public void testMyRunnableWithLambda(){
for (int i = 0; i < 3; i++) {
String threadName="MyRunnable-" + i;
new Thread(() -> {
log.info("通过Lambda表达式实现,子线程【{}】执行,优先级【{}】", threadName, Thread.currentThread().getPriority());
}).start();
}
log.info("通过Lambda表达式实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
}
运行结果:
20:42:22.672 [Thread-1] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,子线程【MyRunnable-1】执行,优先级【5】
20:42:22.672 [Thread-2] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,子线程【MyRunnable-2】执行,优先级【5】
20:42:22.672 [main] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,主线程【main】执行,优先级【5】
20:42:22.672 [Thread-0] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,子线程【MyRunnable-0】执行,优先级【5】
之前的实现多线程的方式可能大家都经常用到,实际还可以使用线程池方式
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class MyExecutor {
@Test
public void testExecutor(){
Executor threadPool = Executors.newFixedThreadPool(5);
for(int i = 0 ;i < 3 ; i++) {
threadPool.execute(new Runnable() {
public void run() {
log.info("通过Executor实现,子线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
}
});
}
log.info("通过Executor实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
}
}
运行结果:
20:52:09.600 [pool-1-thread-1] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,子线程【pool-1-thread-1】执行,优先级【5】
20:52:09.600 [main] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,主线程【main】执行,优先级【5】
20:52:09.600 [pool-1-thread-2] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,子线程【pool-1-thread-2】执行,优先级【5】
20:52:09.600 [pool-1-thread-3] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,子线程【pool-1-thread-3】执行,优先级【5】
当然也能写成 Lambda 表达式的方式
@Test
public void testExecutorWithLambda(){
Executor executorService = Executors.newFixedThreadPool(5);
for(int i = 0 ;i < 3 ; i++) {
executorService.execute(() -> {
log.info("通过Executor和lambda实现,子线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
});
}
log.info("通过Executor和lambda实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
}
使用lambda方式运行结果:
20:52:23.630 [pool-1-thread-1] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,子线程【pool-1-thread-1】执行,优先级【5】
20:52:23.630 [pool-1-thread-3] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,子线程【pool-1-thread-3】执行,优先级【5】
20:52:23.630 [main] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,主线程【main】执行,优先级【5】
20:52:23.630 [pool-1-thread-2] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,子线程【pool-1-thread-2】执行,优先级【5】
spring作为目前最流行的框架之一,同样给我们提供了多线程的方案,我们先写个配置类,注意 @EnableAsync 注解的添加。
package com.alian.csdn.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.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@EnableAsync
@Configuration
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//设置核心线程数
executor.setCorePoolSize(5);
//设置最大线程数
executor.setMaxPoolSize(10);
//设置任务队列容量
executor.setQueueCapacity(10);
//设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
//设置默认线程名称(线程前缀名称,有助于区分不同线程池之间的线程比如:taskExecutor-query-)
executor.setThreadNamePrefix("taskExecutor-async-");
//设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//设置允许核心线程超时
executor.setAllowCoreThreadTimeOut(true);
return executor;
}
}
在写一个业务处理类,定义三个业务方法
package com.alian.csdn.thread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class AsyncThread {
@Async("taskExecutor")
public void taskOne() throws Exception {
log.info("方法taskOne被调用,spring异步多线程@Async,子线程【{}】执行", Thread.currentThread().getName());
}
@Async("taskExecutor")
public void taskTwo() throws Exception {
log.info("方法taskTwo被调用,spring异步多线程@Async,子线程【{}】执行", Thread.currentThread().getName());
}
@Async("taskExecutor")
public void taskThree() throws Exception {
log.info("方法taskThree被调用,spring异步多线程@Async,子线程【{}】执行", Thread.currentThread().getName());
}
}
我们简单点就在main方法里执行测试
package com.alian.csdn;
import com.alian.csdn.thread.AsyncThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@Slf4j
@SpringBootApplication
public class CsdnApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CsdnApplication.class, args);
//获取容器里的对象
AsyncThread bean = context.getBean(AsyncThread.class);
log.info("----------------------同一个方法调用------------------------------");
bean.taskOne();
bean.taskOne();
bean.taskOne();
}
}
运行结果:
21:33:37.498 INFO [ main] com.alian.csdn.CsdnApplication : ----------------------同一个方法调用------------------------------
21:33:37.504 INFO [xecutor-async-3] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-3】执行
21:33:37.504 INFO [xecutor-async-2] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-2】执行
21:33:37.504 INFO [xecutor-async-1] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-1】执行
我们再测试调用不同的方法
package com.alian.csdn;
import com.alian.csdn.thread.AsyncThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@Slf4j
@SpringBootApplication
public class CsdnApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CsdnApplication.class, args);
//获取容器里的对象
AsyncThread bean = context.getBean(AsyncThread.class);
log.info("----------------------不同方法调用------------------------------");
bean.taskOne();
bean.taskTwo();
bean.taskThree();
}
}
运行结果:
21:35:55.264 INFO [ main] com.alian.csdn.CsdnApplication : ----------------------不同方法调用------------------------------
21:35:55.270 INFO [xecutor-async-1] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-1】执行
21:35:55.270 INFO [xecutor-async-2] com.alian.csdn.thread.AsyncThread: 方法taskTwo被调用,spring异步多线程@Async,子线程【taskExecutor-async-2】执行
21:35:55.270 INFO [xecutor-async-3] com.alian.csdn.thread.AsyncThread: 方法taskThree被调用,spring异步多线程@Async,子线程【taskExecutor-async-3】执行
从上述两个结果可以看到,不管是同一个方法还是不同的方法,每次的线程都不一样,更多关于ThreadPoolTaskExecutor的使用可以参考我之前定时任务的文章:Spring Boot定时任务详解(线程池方式)