异步简介
异步编程是一种编程模式,通过将任务分解为多个子任务,并在后台或并行线程中执行这些子任务,以提高程序的性能和响应能力。
一、继承 Thread 类实现异步
1、继承Thread子类需要用到的方法
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行,不能直接调用该方法实现多线程 |
void start() | 使此方法开启一个新线程并开始执行,Java虚拟机会自动调用 run方法 |
2、实现步骤
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
3、举个例子
public class MyThreadDemo extends Thread {
@Override
public void run() {
Long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
Long end = System.currentTimeMillis();
System.out.println("线程耗时:"+(end-start)+"毫秒");
}
}
public static void main(String[] args) {
Long start = System.currentTimeMillis();
MyThreadDemo m1 = new MyThreadDemo();
MyThreadDemo m2 = new MyThreadDemo();
m1.start();
m2.start();
Long end = System.currentTimeMillis();
System.out.println("总线程耗时:"+(end-start)+"毫秒");
}
实现结果:
总结: 继承 Thread 类这种实现方式,实现比较简单,但是扩展性差,因为类只能单继承。
二、通过新建匿名线程来实现异步
1、实现方法
new Thread(()->{
执行代码块;
}).start();
2、举例说明
Long start = System.currentTimeMillis();
new Thread(() -> {
Long start1 = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Long end1 = System.currentTimeMillis();
System.out.println("线程耗时:"+(end1-start1)+"毫秒");
}).start();
Long end = System.currentTimeMillis();
System.out.println("总线程耗时:"+(end-start)+"毫秒");
实现结果:
注意: JDK8以上版本支持该方法
【Java异常】 Error:java: Compilation failed: internal java compiler error 的解决方案
三、实现 Runnable 接口
1、需要用到的 Thread 构造方法介绍:
方法名 | 说明 |
---|---|
Thread(Runnable target) | 传入实现了 Runnable 接口的类,构造一个 Thread 对象 |
Thread(Runnable target, String name) | 传入实现了 Runnable 接口的类,构造一个名称为 name 的 Thread 对象 |
2、实现步骤:
- 定义一个类 MyRunnable 实现 Runnable 接口
- 在 MyRunnable 类中实现 run() 方法
- 创建MyRunnable 类的对象
- 创建 Thread 类的对象,把 MyRunnable 对象作为构造方法的参数
- 启动线程
3、举例说明
public class AsynchronousThread implements Runnable {
@Override
public void run() {
Long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Long end =System.currentTimeMillis();
System.out.println("异步线程耗时:"+(end-start)+"毫秒");
}
}
public static void main(String[] args) {
Long start = System.currentTimeMillis();
AsynchronousThread at= new AsynchronousThread();
Thread thread1= new Thread(at);
Thread thread2 = new Thread(at);
thread1.start();
thread2.start();
Long end =System.currentTimeMillis();
System.out.println("同步线程耗时:"+(end-start)+"毫秒");
}
实现结果:
总结: 实现 Runnable 接口、Callable 接口这两种实现方式,实现比较复杂,但是扩展性比较强。
四、实现 Callable 接口
1、相关方法介绍:
方法名 | 说明 |
---|---|
V call() | 这是 Callable 接口中要实现的方法,相当于 Runnable 接口中的 run 方法 |
FutureTask(Callable callable) | 使用 Callable 接口实现类实例创建一个 FutureTask,它运行时会调配用 Callable 接口中的 call 方法 |
V get() | FutureTask实例的 get 方法,可以阻塞代码继续往下执行,直到获取到异步线程中的返回结果为止 |
2、实现步骤:
- 定义一个类 MyCallable 实现 Callable 接口
- 在 MyCallable 类中重实现 call() 方法
- 创建 MyCallable 类的对象
- 创建 FutureTask 对象,把 MyCallable 对象作为构造方法的参数
- 创建 Thread 类的对象,把 FutureTask 对象作为构造方法的参数
- 启动线程
- 如果想获取返回值的话,可以调用get方法,就可以获取线程结束之后的结果
3、举例说明
public class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
Long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Long end =System.currentTimeMillis();
System.out.println("异步线程耗时:"+(end-start)+"毫秒");
return "返回异步线程耗时:"+(end-start)+"毫秒";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CallableThread mc = new CallableThread();
//因为该类实现了String 类型的 Callable 接口
//所以返回值也是 String 类型,所以创建的是 String 类型的 FutureTask 对象
FutureTask<String> ft = new FutureTask<>(mc);
//传入 FutureTask 实例,创建线程对象
Thread t1 = new Thread(ft);
//不能在这个地方使用 FutureTask 的 get 方法获取异步线程的返回值,否则程序将卡死在这里。
//因为 t1 线程还没有执行,所以无法获取到返回值,所以如果执行 get 方法,程序将卡死在这里。
//String s = ft.get();
//开启新线程,异步执行 MyCallable 实例中的 call 方法逻辑
t1.start();
//这里编写一些实现其它业务逻辑代码进行执行
//可以做一些其它比较耗时的任务
//......
Thread.sleep(2000);
//获取异步线程的返回值
String s = ft.get();
System.out.println(s);
Long end =System.currentTimeMillis();
System.out.println("同步线程耗时:"+(end-start)+"毫秒");
}
返回结果:
总结: 如果想要获取到异步线程中的返回值的话,可以采用实现 Callable 接口这种实现方式。
五、Spring的@Async异步(常用)
1、@Async 注解简介
@Async 注解作用
Spring 框架提供的注解,用于将方法标记为异步执行的方法。它的作用是告诉 Spring 框架在调用被注解的方法时,将其放入线程池中异步执行,而不是阻塞等待方法的完成。
@Async 注解的工作原理
在调用被注解的方法时,Spring 会将该方法的执行转移到线程池中的一个线程进行处理。执行完成后,方法的返回值将通过 Future 或 CompletableFuture 进行封装,以便获取方法的返回结果
源码讲解
2、步骤讲解
- 在方法上添加@Async,表示此方法是异步方法;
- 在类上添加@Async,表示类中的所有方法都是异步方法;
- 使用此注解的类,必须是Spring管理的类;
- 需要在启动类或配置类中加入@EnableAsync注解,@Async才会生效;
- 在使用@Async时,如果不指定线程池的名称,也就是不自定义线程池,@Async是有默认线程池的,使用的是Spring默认的线程池SimpleAsyncTaskExecutor。
默认线程池的默认配置如下:
- 默认核心线程数:8;
- 最大线程数:Integet.MAX_VALUE;//在并发情况下,会无限制的创建线程
- 队列使用LinkedBlockingQueue;
- 容量是:Integet.MAX_VALUE;
- 空闲线程保留时间:60s;
- 线程池拒绝策略:AbortPolicy;
3、Spring 已经实现的线程池
- SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。
- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
- SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
- ThreadPoolTaskExecutor :最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
4、异步的方法有:
- 最简单的异步调用,返回值为void
- 带参数的异步调用,异步方法可以传入参数
- 存在返回值,常调用返回Future
5、实例讲解
启动类
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
自定义线程池
@Configuration
public class AsyncTaskConfig{
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池大小
//设置核心线程数
executor.setCorePoolSize(3);
//最大线程数
executor.setMaxPoolSize(6);
//队列容量
executor.setQueueCapacity(12);
//活跃时间(秒)
executor.setKeepAliveSeconds(60);
//线程名字前缀
executor.setThreadNamePrefix("SyncHistoryInvoice-");
//线程拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//初始化
executor.initialize();
return executor;
}
}
异步任务
@Async("taskExecutor")
public void test() throws Exception {
long sleep = random.nextInt(10000);
log.info("开始任务,需耗时:" + sleep + "毫秒");
Thread.sleep(sleep);
log.info("完成任务");
return new AsyncResult<>("test");
}
调用异步任务
异步任务类名.test();
拒绝策略
- ThreadPoolExecutor.AbortPolicy()抛出java.util.concurrent.RejectedExecutionException异常 终止策略是默认的饱和策略;
- ThreadPoolExecutor.CallerRunsPolicy()当抛出RejectedExecutionException异常时,会调rejectedExecution方法 调用者运行策略实现了一种调节机制,该策略既不会抛弃任务也不会爆出异常,而是将任务退回给调用者,从而降低新任务的流量
- ThreadPoolExecutor.DiscardOldestPolicy()抛弃旧的任务;当新提交的任务无法保存到队列中等待执行时将抛弃最旧的任务,然后尝试提交新任务。如果等待队列是一个优先级队列,抛弃最旧的策略将导致抛弃优先级最高的任务,因此AbortPolicy最好不要和优先级队列一起使用。
- ThreadPoolExecutor.DiscardPolicy()抛弃当前的任务
6、@Async失效的几个原因:
- 注解@Async的方法不是public方法;
- 注解@Async的返回值只能为void或Future;
- 注解@Async方法使用static修饰也会失效;
- 没在启动类上加@EnableAsync注解;
- 调用异步方法和@Async调用方法不能在一个类中;原因: 类似于Spring对@Transactional注解时也有类似问题,Spring扫描时具有@Transactional注解方法的类时,是生成一个代理类,由代理类去开启关闭事务,而在同一个类中,方法调用是在类体内执行的,Spring无法截获这个方法调用。即 @Async使用的是动态代理来实现异步调用,因此不能够在同一个类中进行调用。方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
- 在Async方法上标注@Transactional是没用的,但在Async方法调用的方法上标注@Transcational是有效的;