java异步编程详解

 更新时间:2019年04月02日 09:25:44   作者:Nostalgia_forever  
这篇文章主要介绍了java异步编程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

很多时候我们都希望能够最大的利用资源,比如在进行IO操作的时候尽可能的避免同步阻塞的等待,因为这会浪费CPU的资源。如果在有可读的数据的时候能够通知程序执行读操作甚至由操作系统内核帮助我们完成数据的拷贝,这再好不过了。从NIO到CompletableFuture、Lambda、Fork/Join,java一直在努力让程序尽可能变的异步甚至拥有更高的并行度,这一点一些函数式语言做的比较好,因此java也或多或少的借鉴了某些特性。下面介绍一种非常常用的实现异步操作的方式。

考虑有一个耗时的操作,操作完后会返回一个结果(不管是正常结果还是异常),程序如果想拥有比较好的性能不可能由线程去等待操作的完成,而是应该采用listener模式。jdk并发包里的Future代表了未来的某个结果,当我们向线程池中提交任务的时候会返回该对象。代码例子:

/**
 * jdk1.8之前的Future
 * 
 * @author Administrator
 *
 */
public class JavaFuture {
	public static void main(String[] args) throws Throwable, ExecutionException {
		ExecutorService executor = Executors.newFixedThreadPool(1);
		// Future代表了线程执行完以后的结果,可以通过future获得执行的结果
		// 但是jdk1.8之前的Future有点鸡肋,并不能实现真正的异步,需要阻塞的获取结果,或者不断的轮询
		// 通常我们希望当线程执行完一些耗时的任务后,能够自动的通知我们结果,很遗憾这在原生jdk1.8之前
		// 是不支持的,但是我们可以通过第三方的库实现真正的异步回调
		Future<String> f = executor.submit(new Callable<String>() {
 
			@Override
			public String call() throws Exception {
				System.out.println("task started!");
				Thread.sleep(3000);
				System.out.println("task finished!");
				return "hello";
			}
		});
 
		//此处阻塞main线程
		System.out.println(f.get());
		System.out.println("main thread is blocked");
	}
}

如果想获得耗时操作的结果,可以通过get方法获取,但是该方法会阻塞当前线程,我们可以在做完剩下的某些工作的时候调用get方法试图去获取结果,也可以调用非阻塞的方法isDone来确定操作是否完成,这种方式有点儿类似下面的过程:

这种方式对流程的控制很混乱,但是在jdk1.8之前只提供了这种笨拙的实现方式,以至于很多高性能的框架都实现了自己的一套异步框架,比如Netty和Guava,下面分别介绍下这三种异步的实现方式(包括jdk1.8)。首先是Guava中的实现方式:

package guava;
 
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
 
/**
 * Guava中的Future
 * 
 * @author Administrator
 *
 */
public class GuavaFuture {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newFixedThreadPool(1);
 
		// 使用guava提供的MoreExecutors工具类包装原始的线程池
		ListeningExecutorService listeningExecutor = MoreExecutors.listeningDecorator(executor);
		//向线程池中提交一个任务后,将会返回一个可监听的Future,该Future由Guava框架提供
		ListenableFuture<String> lf = listeningExecutor.submit(new Callable<String>() {
 
			@Override
			public String call() throws Exception {
				System.out.println("task started!");
				//模拟耗时操作
				Thread.sleep(3000);
				System.out.println("task finished!");
				return "hello";
			}
		});
		//添加回调,回调由executor中的线程触发,但也可以指定一个新的线程
		Futures.addCallback(lf, new FutureCallback<String>() {
 
			//耗时任务执行失败后回调该方法
			@Override
			public void onFailure(Throwable t) {
				System.out.println("failure");
			}
			
			//耗时任务执行成功后回调该方法
			@Override
			public void onSuccess(String s) {
				System.out.println("success " + s);
			}
		});
		
		//主线程可以继续做其他的工作
		System.out.println("main thread is running");
	}
}

Guava提供了一套完整的异步框架,核心是可监听的Future,通过注册监听器或者回调方法实现及时获取操作结果的能力。需要提一点的是,假设添加监听的时候耗时操作已经执行完了,此时回调方法会被立即执行并不会丢失。想探究其实现方式的话可以跟一下源码,底层的原理并不难。

谈到异步编程就不得不提一下Promise,很多函数式语言比如js原生支持Promise,但是在java界也有一些promise框架,其中就有大名鼎鼎的Netty。从Future、Callback到Promise甚至线程池,Netty实现了一套完整的异步框架,并且netty代码中也大量使用了Promise,下面是Netty中的例子:

package netty_promise;
 
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
 
/**
 * netty中的promise
 * 
 * @author Administrator
 *
 */
public class PromiseTest {
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) throws Throwable {
		//线程池
		EventExecutorGroup group = new DefaultEventExecutorGroup(1);
		//向线程池中提交任务,并返回Future,该Future是netty自己实现的future
		//位于io.netty.util.concurrent包下,此处运行时的类型为PromiseTask
		Future<?> f = group.submit(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("任务正在执行");
				//模拟耗时操作,比如IO操作
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("任务执行完毕");
			}
		});
		//增加监听
		f.addListener( new FutureListener() {
			@Override
			public void operationComplete(Future arg0) throws Exception {
				System.out.println("ok!!!");
			}
		});
		System.out.println("main thread is running.");
	}
}

直到jdk1.8才算真正支持了异步操作,其中借鉴了某些框架的实现思想,但又有新的功能,同时在jdk1.8中提供了lambda表达式,使得java向函数式语言又靠近了一步。借助jdk原生的CompletableFuture可以实现异步的操作,同时结合lambada表达式大大简化了代码量。代码例子如下:

package netty_promise;
 
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
 
/**
 * 基于jdk1.8实现任务异步处理
 * 
 * @author Administrator
 *
 */
public class JavaPromise {
	public static void main(String[] args) throws Throwable, ExecutionException {
		// 两个线程的线程池
		ExecutorService executor = Executors.newFixedThreadPool(2);
		//jdk1.8之前的实现方式
		CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
			@Override
			public String get() {
				System.out.println("task started!");
				try {
					//模拟耗时操作
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				return "task finished!";
			}
		}, executor);
 
		//采用lambada的实现方式
		future.thenAccept(e -> System.out.println(e + " ok"));
		
		System.out.println("main thread is running");
	}
}

上面的图只是简单的表示了一下异步的实现流程,实际的调用中看似顺序的步骤会发生线程的切换。

以上所述是小编给大家介绍的java异步编程详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关文章

  • spring boot 使用profile来分区配置的操作

    spring boot 使用profile来分区配置的操作

    这篇文章主要介绍了spring boot使用profile来分区配置的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 详解Java Proxy动态代理机制

    详解Java Proxy动态代理机制

    今天给大家带来的是关于Java的相关知识,文章围绕着Java动态代理机制展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Java中的自旋锁spinlock详解

    Java中的自旋锁spinlock详解

    这篇文章主要介绍了Java中的自旋锁spinlock详解,自旋锁就是循环尝试获取锁,不会放弃CPU时间片,减伤cup上下文切换,缺点是循环会消耗cpu,需要的朋友可以参考下
    2024-01-01
  • Spring中的bean概念介绍

    Spring中的bean概念介绍

    这篇文章主要介绍了Spring中的bean相关知识,包括基本概念定义控制反转IOC的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • Java正则替换手机号代码实例

    Java正则替换手机号代码实例

    本文的主要内容是Java语言中正则表达式替换手机号的第4到第7位,实现方法十分简单,同时涉及了一些正则表达式的相关用法,需要的朋友可以参考下。
    2017-09-09
  • Xml中使用foreach遍历对象实现代码

    Xml中使用foreach遍历对象实现代码

    这篇文章主要介绍了Xml中使用foreach遍历对象实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • 10种java数组合并的常用方法总结

    10种java数组合并的常用方法总结

    在Java中,合并(或连接)数组是常见的任务,这篇文章主要为大家整理了10个java中常用的数组合并方法,文中的示例代码简洁易懂,需要的小伙伴可以参考下
    2023-12-12
  • java绘制五子棋棋盘

    java绘制五子棋棋盘

    这篇文章主要为大家详细介绍了java绘制五子棋棋盘,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • Java使用Redis实现秒杀功能

    Java使用Redis实现秒杀功能

    这篇文章主要为大家详细介绍了Java使用Redis实现秒杀功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-09-09
  • Java操作透明图片并保持背景透明的实现

    Java操作透明图片并保持背景透明的实现

    这篇文章主要介绍了Java操作透明图片并保持背景透明的实现,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11

最新评论