异步和同步
我们现在假设一个方法,需要新增一个用户,同时向用户发送一条短信.
同步的方式,就是新增用户和发送短线都是顺序执行,然后执行完才return;
异步的方式,就是新增完用户后直接返回了,发送短信这个动作是异步执行的,处理的时机是不确定的,由CPU决定。
实现多线程
继承Thread类
我们现在,通过继承Thread来实现多线程来达到异步执行任务的效果。
package com.tea.modules.java8.thread.create;
/**
* 使用继承的方式实现多线程
* 1. 通过JVM告诉操作系统创建线程
* 2. 操作系统开辟内存并根据不同平台的创建线程函数创建线程对象
* 3. 操作系统对线程对象进行调度,以确定执行时机
* 4. 线程任务被执行,注意,异步执行并不保证先后顺序
*
* @author jaymin
* @since 2021/9/4 0:21
*/
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println(System.currentTimeMillis() + ":发送信息:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
System.out.println(System.currentTimeMillis() + ":保存用户信息");
MyThread myThread = new MyThread();
// 注意,启动多线程用start而不是run
// 调用start方法最终会调用run方法
myThread.start();
}
}
实现的方式很简单,直接继承Thread
然后重写run
方法即可。这里要注意,启动Thread任务是调用start
,最终会回调触发我们的start
方法.
实现Runnable接口
package com.tea.modules.java8.thread.create;
/**
* 实现Runnable接口来实现多线程
* 使用Runnable更加好,因为JAVA语言不支持多继承,使用接口可以让扩展性更加好.
*
* @author jaymin
* @since 2021/9/4 0:40
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + "发送信息");
}
public static void main(String[] args) {
System.out.println(System.currentTimeMillis() + ":保存用户信息");
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
}
}
实现Runnable接口,然后通过构造函数传入Thread中,也是可以达到异步的效果的。平时更加推荐这种做法,因为Java是单一继承特性,但是接口是可以多实现的,扩展性更好。
实现Callable接口
我们现在有了Runnable的方式去启动一个线程,但是这种方式无法有效地拿到返回值,如果我们希望拿到异步的处理结果(比如说异步去查询数据库的数据),那么我们需要Callable这种方式去实现。
package com.tea.modules.java8.thread.create;
import java.util.concurrent.*;
/**
* com.tea.modules.java8.thread.create
*
* @author jaymin
* @since 2022/4/29
*/
public class MyFuture implements Callable {
@Override
public String call() throws Exception {
System.out.println(System.currentTimeMillis() + ":发送短信");
return "success";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println(System.currentTimeMillis() + ":保存用户信息");
MyFuture myFuture = new MyFuture();
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(myFuture);
String result = future.get();
System.out.println(result);
executorService.shutdown();
}
}
通过实现Callable你可以向线程池提交你的Future,然后通过get方法获取返回值。
注意,这里的get过程是阻塞的.
将Runnable和Callable封装成FutureTask
FutureTask提供了更友好的方式支持异步,他可以包装你的Runnable和Callable任务,获取结果时我们只需要关注FutureTask就行了。
package com.tea.modules.java8.thread.create;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
* com.tea.modules.java8.thread.create
*
* @author jaymin
* @since 2022/4/29
*/
public class MyFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyFuture myFuture = new MyFuture();
FutureTask futureTask = new FutureTask<>(myFuture);
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(futureTask);
String result = futureTask.get();
System.out.println("callable:" + result);
MyRunnable myRunnable = new MyRunnable();
FutureTask runnableTask = new FutureTask<>(myRunnable, "success");
executorService.submit(runnableTask);
System.out.println("runnable:" + runnableTask.get());
executorService.shutdown();
}
}
- Result
1651223699779:发送短信
callable:success
1651223699781:发送信息
runnable:success
CompletableFuture
java8提供了CompletableFuture
让你能用流的方式处理异步,这里给个简单的演示,后续我会详细介绍这个类
package com.tea.modules.java8.thread.completable;
import com.tea.modules.model.po.User;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* com.tea.modules.java8.thread.completable
*
* @author jaymin
* @since 2022/4/29
*/
public class CompletableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
User user = User.builder().age(1).userName("张三").build();
CompletableFuture completableFuture = CompletableFuture.completedFuture(user)
.thenApplyAsync(u -> {
System.out.println("保存信息");
return u;
})
.thenApplyAsync(u -> {
System.out.println("现在向" + u.getUserName() + "发送信息");
return u;
});
completableFuture.get();
completableFuture.join();
}
}
- Result
保存信息
现在向张三发送信息
Thread类
Thread就是多线程在Java语言中的体现,通过其提供的api我们可以去控制线程执行任务的状态.
start-启动线程
start是线程启动的方式,Thread类本身实现了Runnable接口,调用start会触发native方法并回调run方法。
interrupt-中断
注意,中断线程需要使用interrupt方法,调用此方法时,线程会被设置成中断状态,每个线程都有一个boolean属性,每个线程会时不时检查这个标志,以判断线程是否中断。
对一个阻塞状态的线程调用interrupt,会触发InterruptedException.
sleep-休眠
休眠给定的毫秒数,1000即为1秒
守护线程
t.setDaemon(true);
通过以上的方式可以将Thread对象设置为守护线程,守护线程可以理解成一个后台的任务,它负责为其他线程服务,当仅剩下守护线程的时候,JVM就会退出。
异步调用时出异常了怎么办
在异步执行任务的时候,其实你在外层去做try-catch是没用的
package com.tea.modules.java8.thread.create;
/**
* 实现Runnable接口来实现多线程
* 使用Runnable更加好,因为JAVA语言不支持多继承,使用接口可以让扩展性更加好.
*
* @author jaymin
* @since 2021/9/4 0:40
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":发送信息");
throw new RuntimeException("我其实报错了");
}
public static void main(String[] args) {
System.out.println(System.currentTimeMillis() + ":保存用户信息");
try {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
} catch (Exception e) {
System.out.println("线程产生了异常: " + e.getMessage());
throw new RuntimeException(e);
}
}
}
这个时候我们需要为Thread注册一个异常处理器,注意他是全局生效。
package com.tea.modules.java8.thread.create;
/**
* 实现Runnable接口来实现多线程
* 使用Runnable更加好,因为JAVA语言不支持多继承,使用接口可以让扩展性更加好.
*
* @author jaymin
* @since 2021/9/4 0:40
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":发送信息");
throw new RuntimeException("我其实报错了");
}
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
//t参数接收发生异常的线程, e就是该线程中的异常
System.out.println(t.getName() + "线程产生了异常: " + e.getMessage());
}
});
System.out.println(System.currentTimeMillis() + ":保存用户信息");
try {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
} catch (Exception e) {
System.out.println("线程产生了异常: " + e.getMessage());
throw new RuntimeException(e);
}
}
}
总结
本文我向你展示了几种创建多线程的方法,总的来说,我更推荐使用FutureTask
或者CompletableFuture
的方式去实现异步。当然,如果在Spring框架下进行开发,我们可以依赖@Async来实现,只需要简单配置线程池即可。