1 获取又返回结果的 需要用到 callable接口
public class TestTask implements Callable<Student> {
@Override
public Student call() throws Exception {
Thread.sleep(1500);
Student student = new Student();
student.setAge(10);
student.setName("里里");
System.out.println("线程执行"+student.toString());
return student;
}
}
@Data
public class Student {
private String name;
private Integer age;
}
2 创建一个线程池,可以用自定义线程池,也可以直接用那四种线程池
这里创建了一个包含三个线程的固定线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<Student> future = executorService.submit(new TestTask());
try {
if (!future.isDone()) {
System.out.println("Task is processing now, please wait...");
}
Student result = future.get();
System.out.println("Result is: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
线程池的介绍
根据主机情况实现自定义线程池:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class CustomThreadPool {
// 线程池大小
private final int poolSize;
// 任务队列
private final BlockingQueue<Runnable> taskQueue;
// 线程数组
private final Thread[] threads;
/**
* 构造函数
* @param poolSize 线程池大小
*/
public CustomThreadPool(int poolSize) {
this.poolSize = poolSize;
this.taskQueue = new LinkedBlockingQueue<>();
this.threads = new Thread[poolSize];
for (int i = 0; i < poolSize; i++) {
threads[i] = new WorkerThread();
threads[i].start();
}
}
/**
* 提交任务
* @param task 任务
*/
public void execute(Runnable task) {
synchronized (taskQueue) {
taskQueue.offer(task);
taskQueue.notify();
}
}
/**
* 关闭线程池
*/
public void shutdown() {
for (int i = 0; i < poolSize; i++) {
threads[i].interrupt();
}
}
/**
* 工作线程
*/
private class WorkerThread extends Thread {
public void run() {
Runnable task;
while (!Thread.currentThread().isInterrupted()) {
try {
task = taskQueue.poll(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
break;
}
if (task != null) {
task.run();
}
}
}
}
}
也可以通过继承 ThreadPoolExecutor 类来实现一个自定义线程池工具类。ThreadPoolExecutor 是 Java 标准库中提供的一个线程池实现,通过继承它,我们可以实现自定义的线程池。
下面是一个继承 ThreadPoolExecutor 的自定义线程池工具类的实现:
import java.util.concurrent.*;
public class CustomThreadPool extends ThreadPoolExecutor {
public CustomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public static CustomThreadPool newThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, int queueSize) {
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(queueSize);
return new CustomThreadPool(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
// 在执行任务前的操作
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 在执行任务后的操作
}
@Override
protected void terminated() {
super.terminated();
// 线程池关闭时的操作
}
}
在构造函数中,我们调用了父类 ThreadPoolExecutor 的构造函数,用于初始化线程池的一些参数,包括线程池的大小、任务队列、线程池的最大大小、线程存活时间等等。
我们还实现了一个静态方法 newThreadPool(),用于创建一个新的线程池。这个方法接收线程池的核心大小、最大大小、线程存活时间、时间单位和任务队列的大小等参数,然后创建一个 ArrayBlockingQueue 作为任务队列,然后通过父类的构造函数创建一个新的线程池。
在这个自定义线程池工具类中,我们还重写了三个方法:
- beforeExecute():在线程执行任务前执行的方法,可以在这里进行一些初始化操作。
- afterExecute():在线程执行任务后执行的方法,可以在这里进行一些清理操作。
- terminated():在线程池关闭时执行的方法,可以在这里进行一些资源释放操作。
这些方法可以根据需要进行重写。
使用这个自定义线程池工具类时,可以通过调用 execute() 方法来提交任务,如下所示:
CustomThreadPool pool = CustomThreadPool.newThreadPool(5, 10, 60L, TimeUnit.SECONDS, 100);
for (int i = 0; i < 10; i++) {
final int taskNum = i;
pool.execute(() -> {
System.out.println("Task " + taskNum + " is running.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskNum + " is completed.");
});
}
pool.shutdown();
这里我们创建了一个新的线程池,并提交了一些任务。在任务执行时,我们通过重写 beforeExecute() 和 afterExecute() 方法,可以在任务执行前后进行一些操作。在线程池关闭时,我们可以通过重写 terminated() 方法来进行一些资源释放操作
在Java中,执行线程任务通常有两种方式:使用 execute 方法或使用 submit 方法。它们的区别在于:
返回值不同:execute 方法没有返回值,而 submit 方法返回一个 Future 对象。
异常处理不同:如果在执行任务时抛出异常,execute 方法会打印出异常堆栈信息,并且不会捕获异常,而 submit 方法会将异常封装到返回的 Future 对象中,可以通过调用 Future.get() 方法获取异常信息。
任务提交方式不同:execute 方法是将任务直接提交给线程池,而 submit 方法则是将任务封装成一个 Callable 对象,并将其提交给线程池。
因此,如果我们需要对线程执行的结果进行控制并捕获异常,可以使用 submit 方法,如果我们只是需要简单地提交一个任务给线程池,可以使用 execute 方法。
3 使用CompletionService
CompletionService
是Java提供的一种辅助类,它可以用于管理多个Future
对象并发执行的结果。CompletionService
可以帮助我们避免手动迭代多个Future
来获取结果的麻烦,并且可以让我们更方便地处理多个任务的执行结果。
CompletionService
最常用的方法是submit(Callable<T> task)
和take()
。通过submit
方法,我们可以提交一个任务到该CompletionService
中,并返回一个与该任务关联的Future
对象。这个Future
对象可以用来获取任务的结果。但是,与直接使用ExecutorService
不同,CompletionService
会将结果保存到一个阻塞队列中,使用take()
方法可以从阻塞队列中取得第一个完成的任务的结果。
举个例子来说,如果有一个任务列表,我们需要依次异步执行,或者说需要一次性获取所有任务的执行结果,这种情况下可以使用CompletionService
。在这个场景下,我们可以将所有任务提交到CompletionService
中,然后使用take()
方法取出队列中的任务结果。这样可以在处理每个任务的同时,按照完成时间顺序获取任务的结果。
以下是一个使用CompletionService
实现同时处理多个任务的代码示例:
import java.util.concurrent.*;
public class CompletionServiceDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newCachedThreadPool();
CompletionService<Integer> completionService = new ExecutorCompletionService<>(executor);
for (int i = 0;i < 10; i++) {
final int finalI = i;
completionService.submit(() -> finalI);
}
for (int i = 0;i < 10; i++) {
int result = completionService.take().get();
System.out.println("Finished task " + result);
}
executor.shutdown();
}
}
上述代码中,我们先定义ExecutorService
(线程池),然后将其传入ExecutorCompletionService
。接着,我们通过submit()
方法将任务放入CompletionService
中。最后,在主线程中通过循环调用take()
方法来获取结果,因为结果会按照完成时间顺序被加入队列,所以可以方便的保证结果的顺序性。
结合我们的案例:
public class Test {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
// Future<Student> future = executorService.submit(new TestTask());
CompletionService completionService = new ExecutorCompletionService(executorService);
for (int i = 0; i <5 ; i++) {
completionService.submit(new TestTask());
}
for (int i = 0; i < 5; i++) {
System.out.println(completionService.take().get());
}
/*try {
if (!future.isDone()) {
System.out.println("Task is processing now, please wait...");
}
Student result = future.get();
System.out.println("Result is: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}*/
}
}
4 异步
异步和多线程一样,都是为了让程序运行效率更高
多线程指的是在一个程序中同时运行多条线程,这些线程相互独立,可以执行不同的任务。通过多线程,我们可以实现并发处理,提高程序的执行效率。
异步则是一种编程模型,指的是一个任务的执行不需要等待其他任务的结束,而是可以继续执行其他任务或者返回结果。异步通常使用回调函数或者事件驱动来实现。
在实际应用中,多线程和异步经常一起使用来处理并发问题。例如,在一个网络请求中,我们可以使用异步方式向服务器发起请求,同时使用多线程方式处理服务器响应的数据。这样可以提高请求的处理速度,同时也不会阻塞主线程的执行。
实操:
实现方式一 使用@Async注解
在Spring Boot应用中,我们可以使用Spring提供的@Async注解来指定一个方法为异步方法。使用该注解后,Spring会在调用该方法时在另外一个线程中执行方法,并立即将控制权返回给调用方
需要注意的是,在使用@Async注解时,我们需要在应用的配置类上加上@EnableAsync注解,开启Spring的异步机制。示例代码如下:
@Configuration
@EnableAsync
public class AppConfig {
// 配置异步线程池等等相关配置...
}
这里我试过了,在异步方法的bean 或者是在启动类上加这个注解都可以实现异步功能,但是如果不加Enable注解的话,只有@Async注解是不能实现异步功能的。
这两处地方都可以。
另外,异步是真异步,怎么讲呢
@RequestMapping("/hello2")
public String hello2() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(2000);
String result = testRetryService.hh();
long end = System.currentTimeMillis();
System.out.println("执行耗时"+((end-start)/1000));
return result;
}
@Async
public String hh() throws InterruptedException {
Thread.sleep(5000);
System.out.println("异步方法输出");
return "Hello";
}
即使我再结果中返回异步执行的结果,他并不会等待异步任务完成再返回,也就是上述代码,在调用接口两秒后就返回(返回为空,因为并没有获取到异步的执行结果异步执行耗时长),并不是七秒。
实现方式二
使用CompletableFuture
除了使用@Async注解以外,Spring Boot还支持Java 8中的CompletableFuture来实现异步操作。CompletableFuture是一种非常强大、灵活的异步编程工具,可以方便地实现链式异步处理。
示例代码如下:
public class AsyncService {
public CompletableFuture<String> doTask1() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步执行的任务1
return "Result1";
});
return future;
}
public CompletableFuture<String> doTask2() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步执行的任务2
return "Result2";
});
return future;
}
}
在上述代码中,我们首先使用CompletableFuture的supplyAsync方法将一个Lambda函数包装为异步执行的任务,并返回一个CompletableFuture对象。接着,我们可以在需要异步执行的地方使用该方法,结合其他的CompletableFuture方法(如thenCompose()、thenApply()等)进行链式处理。
需要注意的是,使用CompletableFuture时,你需要手动指定线程池来执行异步任务。示例代码如下:
public class AsyncService {
private static final Executor executor = Executors.newFixedThreadPool(5); // 线程池
public CompletableFuture<String> doTask1() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步执行的任务1
return "Result1";
}, executor);
return future;
}
public CompletableFuture<String> doTask2() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步执行的任务2
return "Result2";
}, executor);
return future;
}
}
附一篇文章,关于实现异步的几种方式:
面试官:实现异步的8种方式,你知道几个?