精华内容
下载资源
问答
  • 2021-03-06 23:56:08

    Java异步编程是什么,如何实现

    时间:2017-10-12     来源:华清远见Java培训中心

    异步编程是Java中比较难理解的知识点,很多朋友对于Java异步编程了解的都不是很透彻。那么Java异步编程是什么?如何实现Java异步编程呢?今天华清Java学院小编就和大家梳理一下Java异步编程的相关知识,希望对大家有所帮助。

    异步编程提供了一个非阻塞的,事件驱动的编程模型。 这种编程模型利用系统中多核执行任务来提供并行,因此提供了应用的吞吐率。此处吞吐率是指在单位时间内所做任务的数量。 在这种编程方式下,一个工作单元将独立于主应用线程而执行, 并且会将它的状态通知调用线程:成功,处理中或者失败。

    我们需要异步来消除阻塞模型。其实异步编程模型可以使用同样的线程来处理多个请求, 这些请求不会阻塞这个线程。想象一个应用正在使用的线程正在执行任务, 然后等待任务完成才进行下一步。 log框架就是一个很好的例子:典型地你想将异常和错误日志记录到一个目标中, 比如文件,数据库或者其它类似地方。你不会让你的程序等待日志写完才执行,否则程序的响应就会受到影响。 相反,如果对log框架的调用是异步地,应用就可以并发执行其它任务而无需等待。这是一个非阻塞执行的例子。

    为了在Java中实现异步编程,你需要使用Future 和 FutureTask, 它们位于java.util.concurrent包下. Future是一个接口而FutureTask是它的一个实现类。实际上,如果在你的代码中使用Future, 你的异步任务会立即执行, 并且调用线程可以得到结果promise。

    下面的代码片段定义了一个包含两个方法的接口。 一个是同步方法,另外一个是异步方法。

    import java.util.concurrent.Future;

    public interface IDataManager {

    // synchronous method

    public String getDataSynchronously();

    // asynchronous method

    public Future getDataAsynchronously();

    }

    值得注意的是回调模型的弊端就是当回调嵌套时很麻烦。为了方便测试, 你应该在代码中将功能从多线程中隔离出来。当在Java中编写异步代码时,你应该遵循异步模型,这样调用线程就不会被阻塞。

    注意构造函数不能是异步的,你不应该在构造函数中调用异步方法。当任务互相不依赖时异步方式尤其有用。当调用任务依赖被调用任务时不应该使用异步(译者按:这对异步来说无意义,因为业务上调用线程被阻塞了).

    你应该在异步方法中处理异常. 你不应该为长时间的task实现异常. 一个长时间运行的任务,如果异步执行的话, 可能会比同步执行耗费更长的时间, 因为运行时要为异步执行的方法执行线程上下文的切换, 线程状态的存储等. 你也应该注意同步的异常和异步的异常有所不同。 同步异常暗示 每次程序执行到那个程序特殊状态时就会抛出异常;异步异常的跟踪则困难的多。所以同步和异步异常暗示同步或异步代码可能抛出异常(synchronous and asynchronous exceptions imply synchronous or asynchronous code in your program that might raise exceptions.)。

    异步对于设计大规模快速响应的应用是至关重要的。异步回调模型带来了很多的好处;你可以依赖你的异步回调方法来执行昂贵的I/O操作而你的处理器可以执行其它任务。然而虽然在Java和C#中提供了异步的支持,异步编程并不总是那么容易实现。异步方式的使用要谨慎: 你应该清楚什么时候可以用和什么时候不该用。

    更多相关内容
  • java异步编程详解

    2020-08-26 04:17:41
    主要介绍了java异步编程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • java异步编程框架

    2019-02-16 15:08:03
    Vert.x最大的特点就在于异步(底层基于Netty),通过事件循环(EventLoop)来调起存储在异步任务队列(CallBackQueue)中的任务,大大降低了传统阻塞模型中线程对于操作系统的开销。因此相比较传统的阻塞模型,异步...
  • API是用于Java异步编程的高级API。 该API支持将多个异步计算流水线化(也称为链接或合并)成单个结果,而不会造成嵌套回调(“ callback hell”)的混乱。 此API也是Java中Future / Promise并发构造的实现。 从Java ...
  • Java异步编程框架Promise的介绍,对于理解java异步编程有哦帮助。
  • 异步编程提供了一个非阻塞的,事件驱动的编程模型。下面通过本文给大家介绍Java 异步编程实践,感兴趣的的朋友一起看看吧
  • java异步编程

    万次阅读 多人点赞 2015-11-15 22:11:01
    很多时候我们都希望能够最大的利用资源,比如在进行IO操作的时候...从NIO到CompletableFuture、Lambda、Fork/Join,java一直在努力让程序尽可能变的异步甚至拥有更高的并行度,这一点一些函数式语言做的比较好,因此jav

            很多时候我们都希望能够最大的利用资源,比如在进行IO操作的时候尽可能的避免同步阻塞的等待,因为这会浪费CPU的资源。如果在有可读的数据的时候能够通知程序执行读操作甚至由操作系统内核帮助我们完成数据的拷贝,这再好不过了。从NIO到CompletableFuture、LambdaFork/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异步编程佳实践

    2020-12-22 21:25:48
     异步编程提供了一个非阻塞的,事件驱动的编程模型。 这种编程模型利用系统中多核执行任务来提供并行,因此提供了应用的吞吐率。此处吞吐率是指在单位时间内所做任务的数量。 在这种编程方式下, 一个工作单元将...
  • Java 异步编程:从 Future 到 Loom

    千次阅读 2019-10-21 11:28:12
    Java 异步编程:从 Future 到 Loom     本文对我们了解异步编程有很好的指导性,稍长,希望大家耐心阅读。     众所周知,Java 开始方法执行到结束,都是由同一个线程完成的。这种方式虽易于开发调试,但...

    Java 异步编程:从 Future 到 Loom

        本文对我们了解异步编程有很好的指导性,稍长,希望大家耐心阅读。

        众所周知,Java 开始方法执行到结束,都是由同一个线程完成的。这种方式虽易于开发调试,但容易因为锁、IO 等原因导致线程挂起,产生线程上下文切换。随着对应用并发能力要求越来越高,频繁的线程上下文切换所带来的成本变得难以忽视。同时,线程也是相对宝贵的资源,无限制的增加线程是不可能的。优秀的技术人员应该能让应用使用更少的线程资源实现更高的并发能力。这便是我们今天要讨论的话题 —— Java 异步编程技术。

        异步编程其实并没有清晰定义。通常我们认为,从方法开始到结束都必须在同一个线程内调度执行的编程方式可被认为是同步编程方式。但因为这样的方式是我们习以为常的,所以也就没有专门名字去称呼它。与这种同步方式相对的,便是异步。即方法的开始到结束可以由不同的线程调度执行的编程方式,被成为异步编程。

        异步编程技术目的,重点并非提高并发能力,而是提高伸缩性 (Scalability)。现在的 Web 服务,应付 QPS 几百上千,甚至上万的场景并没有太大问题,但问题是如何在并发请求量突增的场景中提供稳定服务呢?如果一个应用能稳定提供 QPS 1000的服务。假如在某一个大促活动中,这个应用的 QPS 突然增加到10000怎么办?或者 QPS 没变,但这个应用所依赖的服务发生故障,或网络超时。当这些情况发生时,服务还能稳定提供吗?虽然熔断、限流等技术能够解决这种场景下服务的可用性问题,但这毕竟是一种舍车保帅的做法。是否能在流量突增时仍保证服务质量呢?答案是肯定的,那就是异步编程 + NIO。NIO 技术本身现在已经很成熟了,关键是用一种什么样的异步编程技术将 NIO 落地到系统,尤其是业务快速迭代的前台、中台系统中。

        这就是本文讨论 Java 异步编程的原因。Java 应用开发领域究竟有哪些技术可以用来提升系统的伸缩性?本文将按照这些技术的演化历程,介绍一下这些技术的意义和演化过程:

    • Future
    • Callback
    • Servlet 3.0
    • Reactive响应式编程
    • Kotlin 协程
    • Project Loom

    一、Future

        J.U.C 中的 Future 算是 Java 对异步编程的第一个解决方案。当向线程池 submit 一个任务后,这个任务便被另一个线程执行了:

    Future future = threadPool.submit(() -> {
      foobar();
      return result;
    });
    
    Object result = future.get();
    

    但这个解决方案有很多缺陷:

    无法方便得知任务何时完成
    无法方便获得任务结果
    在主线程获得任务结果会导致主线程阻塞

    二、Callback

        为了解决使用 Future 所存在的问题,人们提出了一个叫 Callback 的解决方案。比如 Google Guava 包中的 ListenableFuture 就是基于此实现的:

    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
    ListenableFuture<Explosion> explosion = service.submit(new Callable<Explosion>() {
      public Explosion call() {
        return pushBigRedButton();
      }
    });
    Futures.addCallback(explosion, new FutureCallback<Explosion>() {
      // we want this handler to run immediately after we push the big red button!
      public void onSuccess(Explosion explosion) {
        walkAwayFrom(explosion);
      }
      public void onFailure(Throwable thrown) {
        battleArchNemesis(); // escaped the explosion!
      }
    });
    

        通过执行 ListenableFuture<Explosion> explosion = service.submit(new Callable<Explosion>() {}) 创建异步任务。通过 Futures.addCallback(explosion, new FutureCallback<Explosion>() {} 添加处理结果的回调函数。这样避免获取并处理异步任务执行结果阻塞调起线程的问题。Callback 是将任务执行结果作为接口的入参,在任务完成时回调 Callback 接口,执行后续任务,从而解决纯 Future 方案无法方便获得任务执行结果的问题。

        但 Callback 产生了新的问题,那就是代码可读性的问题。因为使用 Callback 之后,代码的字面形式和其所表达的业务含义不匹配,即业务的先后关系到了代码层面变成了包含和被包含的关系。

        因此,如果大量使用 Callback 机制,将使大量的应该是先后的业务逻辑在代码形式上表现为层层嵌套。这会导致代码难以理解和维护。这便是所谓的 Callback Hell(回调地狱)问题。

        Callback Hell 问题可以从两个方向进行一定的解决:一是事件驱动机制、二是链式调用。前者被如 Vert.x 所使用,后者被 CompletableFuture、反应式编程等技术采用。但这些优化的效果有限,不能根本上解决 Callback 机制所带来的代码可维护性的下降。

    Callback 与 NIO

        Callback 真正体现价值,是它与 NIO 技术结合之后。原因也很简单:对于 CPU 密集型应用,采用 Callback 风格没有意义;对于 IO 密集型应用,如果是使用 BIO,Callback 同样没有意义,因为最终会有一个线程是因为 IO 而阻塞。而只有使用 NIO 才能避免线程阻塞,也必须使用 Callback 风格,才能使应用得以被开发出来。NIO 的广泛应用是在 Apache Mina、JBoss Netty 等技术出现之后。这些技术很大程度地简化了 NIO 技术的使用,但直接使用它们开发业务系统还是很繁琐。

        下面看一个真实的例子。这个例子背后的完整应用的功能是将微软 Exchange 服务接口(Exchange Web Service)转换为 Rest 风格的接口,下面这段代码是这个应用的一部分。

    public class EwsCalendarHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(final ChannelHandlerContext ctx, Object msg) {
            if (msg instanceof HttpRequest) {
                final HttpRequest origReq = (HttpRequest) msg;
    
                HttpRequest request = translateRequest(origReq);
    
                if (backendChannel == null) {
                    connectBackendFuture = connectBackend(ctx, StaticConfiguration.EXCHANGE_PORT);
                    sendMessageAfterConnected(ctx, request);
                } else if (backendChannel.isActive()) {
                    setHttpRequestToBackendHandler(request);
                    sendObjectAndFlush(ctx, request);
                } else {
                    sendMessageAfterConnected(ctx, request);
                }
            } else if (msg instanceof HttpContent) {
                HttpContent content = (HttpContent) msg;
                if (backendChannel == null || !backendChannel.isActive()) {
                    sendMessageAfterConnected(ctx, content);
                } else {
                    sendObjectAndFlush(ctx, content);
                }
            }
        }
        
        private void sendMessageAfterConnected(final ChannelHandlerContext ctx, final HttpObject message) {
            if (connectBackendFuture == null) {
                LOGGER.warn("next hop connect future is null, drop the message and return: {}", message);
                return;
            }
    
            connectBackendFuture.addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    ChannelFuture f = sendObjectAndFlush(ctx, message);
                    if (f != null) {
                        f.addListener((future1) ->
                                backendChannel.attr(FIND_ITEM_START_ATTR_KEY).set(System.currentTimeMillis())
                        );
                    }
    
                }
            });
        }
    }
    

        在方法 sendMessageAfterConnected 中,我们已经能看到嵌套两层的 Callback。而上面实例中的 EwsCalendarHandler 所实现的 ChannelInboundHandler 接口,本质上也是一个回调接口。

        其实上面的例子只有一级服务调用。在微服务流行的今天,多级服务调用很常见,一个服务先调 A,再用结果 A 调 B,然后用结果 B 调用 C,等等。这样的场景,如果直接用 Netty 开发,技术难度会比传统方式增加很多。这其中的难度来自两方面,一是 NIO 和 Netty 本身的技术难度,二是 Callback 风格所导致的代码理解和维护的困难。

        因此,直接使用 Netty,通常局限在基础架构层面,在前台和中台业务系统中,应用较少。

    三、Servlet 3.0

        上面讲到,如果直接使用 Netty 开发应用,将不可避免地遇到 Netty 和 NIO 本身的技术挑战,以及 Callback Hell 问题。对于前者,Servlet 3.0 提供了一个解决方案。

    ▼ 示例:Servlet 3.0 ▼

    @WebServlet(urlPatterns = "/demo", asyncSupported = true)
    public class AsyncDemoServlet extends HttpServlet {
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
            // Do Something
     
            AsyncContext ctx = req.startAsync();
            startAsyncTask(ctx);
        }
    }
     
    private void startAsyncTask(AsyncContext ctx) {
        requestRpcService(result -> {
            try {
                PrintWriter out = ctx.getResponse().getWriter();
                out.println(result);
                out.flush();
                ctx.complete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
    

        Servlet 3.0 的出现,解决了在过去基于 Servlet 的 Web 应用中,接受请求和返回响应必须在同一个线程的问题,实现了如下目标:

    可以避免了 Web 容器的线程被阻塞挂起
    使请求接收之后的任务处理可由专门线程完成
    不同任务可以实现线程池隔离
    结合 NIO 技术实现更高效的 Web 服务
    除了直接使用 Servlet 3.0,也可以选择 Spring MVC 的 Deferred Result。

    ▼ 示例:Spring MVC DeferredResult ▼

    @GetMapping("/async-deferredresult")
    public DeferredResult<ResponseEntity<?>> handleReqDefResult(Model model) {
        LOG.info("Received async-deferredresult request");
        DeferredResult<ResponseEntity<?>> output = new DeferredResult<>();
         
        ForkJoinPool.commonPool().submit(() -> {
            LOG.info("Processing in separate thread");
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
            }
            output.setResult(ResponseEntity.ok("ok"));
        });
         
        LOG.info("servlet thread freed");
        return output;
    }
    

    Servlet 3.0 的技术局限

        Servlet 3.0 并不是用来解决前面提到的 Callback Hell 问题的,它只是降低了异步 Web 编程的技术门槛。对于 Callback Hell 问题,使用 Servlet 3.0 或类似技术时同样会遇到。解决 Callback Hell 还需另寻他法。

    四、响(反)应式编程

        现在挡在异步编程最大的障碍就是 Callback Hell,因为 Callback Hell 对代码可读性有很大杀伤力。而本节介绍的反应式编程技术,除了响应性、伸缩性、容错性以外,从开发人员的角度来讲,就是代码可读性要比 Callback 提升了许多。

    ▼ 图:反应式编程的特性 ▼
    在这里插入图片描述

    ▼ 反应式编程简单示例 ▼

    userService.getFavorites(userId)
               .flatMap(favoriteService::getDetails)
               .switchIfEmpty(suggestionService.getSuggestions())
               .take(5)
               .publishOn(UiUtils.uiThreadScheduler())
               .subscribe(uiList::show, UiUtils::errorPopup);
    

        可读性的提高原因在于反应式编程可让开发人员将实现业务的各种方法使用链式算子串联起来,而串联起来的各种方法的先后关系与执行顺序大体一致。

        这其实是采用了函数式编程的设计,通过函数式编程解决了之前 Callback 设计存在的代码可读性问题。

        虽然相对于 Callback,代码可读性是反应式编程的优点,但这种优点是相对的,相对于传统代码,可读性就成了反应式编程的缺点。上面的例子代码看上去还容易理解,但换成下面的例子,大家就又能重新看到 Callback Hell 的影子了:

    ▼ 示例:查询最近邮件数(反应式编程版) ▼

    @GetMapping("/reactive/{personId}")
    fun getMessagesFor(@PathVariable personId: String): Mono<String> {
      return peopleRepository.findById(personId)
          .switchIfEmpty(Mono.error(NoSuchElementException()))
          .flatMap { person ->
              auditRepository.findByEmail(person.email)
                  .flatMap { lastLogin ->
                      messageRepository.countByMessageDateGreaterThanAndEmail(lastLogin.eventDate, person.email)
                          .map { numberOfMessages ->
                              "Hello ${person.name}, you have $numberOfMessages messages since ${lastLogin.eventDate}"
                          }
                  }
          }
    }
    

        因此,反应式编程只看代码形式,可以被视为 Callback 2.0。解决了之前的一些问题,但并不彻底。

        目前,在 Java 领域实现了反应式编程的技术有 Spring 的 Project Reactor、Netflix RxJava 1/2 等。前者的 3.0 版本作为 Spring 5 的基础,在17年底发布,推动了后端领域反应式编程的发展。后者出现时间更早,在前端开发领域应用的比后端更要广泛一些。

        除了开源框架,JDK 也提供了对反应式编程解决方案:JDK 8 的 CompletableFuture 不算是反应式编程,但是它在形式上带有一些反应式编程的函数式代码风格。JDK 9 Flow 实现了 Reactive Streams 规范,但是实施反应式编程需要完整的解决方案,单靠 Flow 是不够的,还是需要 Project Reactor 这样的完整解决方案。但 JDK 层面的技术能提供统一的技术抽象和实现,在统一技术方面还是有积极意义的。

    反应式编程的应用范围

        正如前面所说,反应式编程仍然存在代码可读性的问题,这个问题在加上反应式编程本身的技术门槛,使得用反应式编程技术在业务系统开发领域一直没有流行普及。但是对于核心系统、底层系统,反应式编程技术所带来的伸缩性、容错性的提升同其增加的开发成本相比通常是可以接受。因此核心系统、底层系统是适合采用反应式编程技术的。

    五、Kotlin 协程

        前面介绍的各种技术,都有明显的缺陷:Future 不是真异步;Callback 可读性差;Servlet 3.0 等技术没能解决 Callback 的缺陷;反应式编程还是难以编写复杂业务。到了18年,一种新的 JVM 编程语言开始流行:Kotlin。Kotlin 首先流行在 Android 开发领域,因为它得到了 Google 的首肯和支持。但对于后端开发领域,因为一项特性,使得 Kotlin 也非常值得注意。那就是 Kotlin Coroutine(后文称 Kotlin 协程)。对于这项技术,我已经写过三篇文章,分别介绍入门、原理和与 Spring Project Reactor 的整合方式。感兴趣的同学可以去简书和微信公众号上去看这些文章(搜索“编走编想”)。

        协程技术不是什么新技术,它在很多语言中都有实现,比如大家所熟悉的 Python、Lua、Go 都是支持协程的。在不同语言中,协程的实现方法各有不同。因为 Kotlin 的运行依赖于 JVM,不能对 JVM 进行修改,因此,Kotlin 不能在底层支持协程。同时,Kotlin 是一门编程语言,需要在语言层面支持协程,而不是像框架那样在语言层面之上支持。因此,Kotlin 对协程支持最核心的部分是在编译器中。因为对这部分原理的解释在之前文章中都有涉及,因此不在这里重复。

        使用 Kotlin 协程之后最大的好处是异步代码的可读性大大提高。如果上一个示例用 Kotlin 协程实现,那就是下面的样子:

    ▼ 示例:查询最近邮件数(Kotlin 协程版) ▼

    @GetMapping("/coroutine/{personId}")
    fun getNumberOfMessages(@PathVariable personId: String) = mono(Unconfined) {
        val person = peopleRepository.findById(personId).awaitFirstOrDefault(null)
                ?: throw NoSuchElementException("No person can be found by $personId")
    
        val lastLoginDate = auditRepository.findByEmail(person.email).awaitSingle().eventDate
    
        val numberOfMessages =
                messageRepository.countByMessageDateGreaterThanAndEmail(lastLoginDate, person.email).awaitSingle()
    
        "Hello ${person.name}, you have $numberOfMessages messages since $lastLoginDate"
    }
    

        目前在 Spring 应用中使用 Kotlin 协程还有些小繁琐,但在 Spring Boot 2.2 中,可以直接在 Spring WebFlux 方法上使用 suspend 关键字。

        Kotlin 协程最大的意义就是可以用看似指令式编程方式(Imperative Programming,即传统编程方式)去写异步编程代码。并发和代码可读性似乎两全其美了。

    Kotlin 协程的局限性

        但事情不是那么完美。Kotlin 协程依赖于各种基于 Callback 的技术。像上面的例子,之所以可以用 Kotlin 协程,是因为上一个版本使用了反应式编程技术。所以,只有当一段代码使用了 ListenableFuture、CompletableFuture、Project Reactor、RxJava 等技术时,才能用 Kotlin 协程进行改造优化。那对于其它的会阻塞线程的技术,如 Object.wait、Thread.sleep、Lock、BIO 等,Kotlin 协程就无能为力了。

        另外一个局限性源于 Kotlin 本身。虽然 Kotlin 兼容 Java,但这种兼容并非完美。因此,对于组件,尤其是基础组件的开发,并不推荐使用 Kotlin,而是更推荐使用 Java。这也导致 Kotlin 协程的使用范围被进一步地限制。

    六、Project Loom

        前面讲到,虽然 Kotlin 协程看上去很好,但在使用上还是有着种种限制。那有没有更好的选择呢?答案是 Project Loom。这个项目在18年底的时候已经达到可初步演示的原型阶段。不同于之前的方案,Project Loom 是从 JVM 层面对多线程技术进行彻底的改变。

        Project Loom 设计思想与之前的一个开源 Java 协程技术非常相似。这个技术就是 Quasar Fiber。而现在 Project Loom 的主要设计开发人员 Ron Pressler 就是来自 Quasar Fiber。

        这里建议大家读一下 Project Loom 的这篇文档:Project Loom: Fibers and Continuations for the Java Virtual Machine。这篇文档介绍了发起 Project Loom 的原因,以及 Java 线程基础的很多底层设计。

        其实发起 Project Loom 的原因也很简单:长期以来,Java 的线程是与操作系统的线程一一对应的,这限制了 Java 平台并发能力的提升。各种框架或其它 JVM 编程语言的解决方案,都在使用场景上有限制。例如 Kotlin 协程必须基于各种 Callback 技术,而 Callback 技术有存在编写、调试困难的问题。为了使 Java 并发能力在更大范围上得到提升,从底层进行改进便是必然。

        下面这幅图很好地展示了目前 Java 并发编程方面的困境,简单的代码并发、伸缩能力差;并发、伸缩能力强的代码复杂,难以与现有代码整合。
    在这里插入图片描述
    为了让简单和高并发这两个目标兼得,我们需要 Project Loom 这个项目。

    使用方法

        在引入 Project Loom 之后,JDK 将引入一个新类:java.lang.Fiber。此类与 java.lang.Thread 一起,都成为了 java.lang.Strand 的子类。即线程变成了一个虚拟的概念,有两种实现方法:Fiber 所表示的轻量线程和 Thread 所表示的传统的重量级线程。

    对于应用开发人员,使用 Project Loom 很简单:

    Fiber f = Fiber.schedule(() -> {
      println("Hello 1");
      lock.lock(); // 等待锁不会挂起线程
      try {
          println("Hello 2");
      } finally {
          lock.unlock();
      }
      println("Hello 3");
    })
    

        只需执行 Fiber.schedule(Runnable task) 就能在 Fiber 中执行任务。最重要的是,上面例子中的 lock.lock() 操作将不再挂起底层线程。除了 Lock 不再挂起线程以外,像 Socket BIO 操作也不再挂起线程。 但 synchronized,以及 Native 方法中线程挂起操作无法避免。

    synchronized (monitor) {
      // 在 Fiber 中调用这条语句还是会挂起线程。
      socket.getInputStream().read();
    }
    

        如上所示,Fiber 的使用非常简单。因此,让现有系统使用 Project Loom 很容易。像 Tomcat、Jetty 这样的 Web 容器,只需将处理请求操作从使用 ThreadPoolExecutor execute 或 submit 改为使用 Fiber schedule 即可。这个youtube视频中的 Demo 展示了 Jetty 使用 Project Loom 改造之后并发吞吐能力的大幅提升。

    实现原理

        接下来简单介绍一下 Project Loom 的实现原理。Project Loom 的使用主要基于 Fiber,而实现则主要基于 Continuation。Contiuation 表示一个可暂停和恢复的计算单元。在 Project Loom 中,Continuationn 使用 java.lang.Continuation 类实现。这个类主要供类库实现使用,而不是直接被应用开发人员使用。Continuation 主要内容如下所示:

    package java.lang;
    
    public class Continuation implements Runnable {
      public Continuation(ContinuationScope scope, Runnable target)
      
      public final void run()
      
      public static void yield(ContinuationScope scope)
      
      public boolean isDone()
    }
    

        Continuation 实现了 Runnable 接口,构造时除了需要提供一个 Runnable 类型的参数以外,还需要提供一个 java.lang.ContinuationScope 的参数。ContinuationScope 顾名思义表示 Continuation 的范围。Continuation 可以被想象成是一个方法执行过程,方法可以调用其它方法。同时,方法执行也有一定的影响范围,如 try…catch 就规定了相应的范围。ContinuationScope 就起到了起到了相应的作用。

        Continuation 有两个最重要的方法:run 和 yield。run 方法首次被调用时,就会执行 Runnable target 的 run 方法。但是,在调用了 yield 方法后,再次调用 run 方法,Continuation 就不会从头执行,而是从 yield 的位置开始执行。

    为了更形象的理解,下面看一个例子:

    Continuation con = new Continuation(SCOPE, () -> {
      println("A");
      Continuation.yield(SCOPE);
      println("B");
      Continuation.yield(SCOPE);
      println("C");
    });
    
    con.run();
    con.run();
    con.run();
    

    输出结果:

    A
    B
    C
        上面的例子非常简单:创建一个 Continuation,其 Runnable target 打印 A、B、C,并在其中 yield 两次。创建之后调用三次 run() 方法。如果这样执行一个普通的 Runnable,那应该打印三次 A、B、C,一共打印九次。而 Continuation 在 yield 之后执行 run,会从 yield 的位置往后执行,而不是从头开始。

    Continuation yield 类似 Thread 的 yield,但前者需要显式调用 run 方法恢复执行。

    在 Project Loom 之后,LockSupport 的 park 操作将变为:

    public class LockSupport {
      var strand = Strands.currentStrand();
      if (strand instanceof Fiber) {
        Continuation.yield(FIBER_SCOPE);
      } else {
        Unsafe.park(false, 0L);
      }
    }
    

    七、展望
        Java 作为使用率最高的编程软件,在包括后端开发、手机应用开发、大数据等众多领域均有广泛应用。但毕竟是一门诞生20多年的编程语言,存在一些现在看来设计上的不足和受到后来者的挑战都是正常。但必须说明,我们口中的 Java 并非一门单纯的编程语言。而应该被视为 Java 语言 + JVM + Java 类库三部分组成。这三部分中,毫无疑问,JVM 是基础。但 JVM 设计之初就并非和 Java 语言紧密绑定,紧密绑定的只是字节码。由任何编程语言编译得到的合法字节码都能运行在 JVM 之上。这使得 Java 语言层面设计的不足可有其它编程语言解决,于是出现了 Groovy、Scala、Kotlin、Clojure 等众多 JVM 语言。这些语言很大程度上弥补了 Java 的不足。

        但像多线程这样的技术,由于和底层虚机和操作系统有千丝万缕的联系,想要彻底改进,绕不开底层优化。这就是 Project Loom 出现的原因。相信 Project Loom 技术会将 Java 的并发能力提升至和 Golang 一样的水平,而付出的成本只是对现有项目的少量改动。

        Azul 的 Deputy CTO Simon Ritter 曾透露 Project Loom 很可能在 Java 13 时发布。 不过可惜Project Loom 未能赶上 Java 13这趟列车,好在 Java 14 的特性还未完全确定,说不定可以上末班车。

        就算 Project Loom 短期没能出世,但目前反应式编程的趋势也非常明显。随着新版本的 Spring 和 Kotlin 的发布,反应式编程的使用、调试变得越来越简单。Dubbo 也明确表示在 3.0 中将会支持 Project Reactor。R2DBC 在不久的未来也会支持 MySQL。因此,Java 异步编程将快速发展,在易用性方面迅速赶上 Go。

        另一方面,开发人员也不要将自己局限在某种特定技术上,对各种技术都保持开放的态度是开发人员技能不断提高的前提。只会简单说某某语言、某某技术比其它技术更好的技术人员永远不会成为出色的技术人员。

    原文作者:编走编想
    链接:https://www.jianshu.com/p/5db701a764cb

    展开全文
  • 畅聊Java异步编程

    2020-01-01 22:59:00
    趁着阿里的“加多”大佬推出了自己的新书《Java异步编程实战》,我也来简单聊下自己对异步编程的了解吧。 我是如何接触到异步编程的呢? 目前,我其实也是一个刚入职场不久的年轻人,对于异步编程的了解和使用有限...

    趁着阿里的“加多”大佬推出了自己的新书《Java异步编程实战》,我也来简单聊下自己对异步编程的了解吧。

    我是如何接触到异步编程的呢?

    目前,我其实是一个刚入职场不久的年轻人,对于异步编程的了解和使用有限。在实际的工作中使用最多的异步编程,就是创建一个线程池来执行一个异步任务,使得主线程可以继续往下执行,不被阻塞。那么,我是如何接触到异步编程技术的呢?

    一切还得从3年前,那个“腥风血雨”的校园招聘说起。相信大多数的小伙伴都是从接触Java并发编程开始的,当然我也不另外。在校园招聘的时候,为了应对面试官,通过面试环节,我对JUC并发包中的内容进行了比较详细的背诵,对于一些知识点都“朗朗上口”,就问你强不强?

    在校园招聘期间,我对线程池有了一定的理论了解,知道线程池可以执行一些异步任务,并且对于Future可以用来获取异步任务的执行结果也都有所耳闻。在面试期间,我也会给面试官进行解析(背诵)这些知识点,嘿嘿。

    工作之后,都在什么场景下使用异步编程呢?

    其实吧,我在实际工作中需要使用异步编程的场景不太多,一般情况下我们所执行的任务还是比较重要的,所以都要求同步来执行(不知道是不是技术不够,导致影响了效率都不清楚,尴尬.jpg)。

    工作中,使用异步编程的场景大概就是客户端请求服务端一个接口,服务端做不到在短时间内返回该结果。所以,服务端启动了一个异步任务(线程)去执行该请求的具体任务,该请求可以直接返回。然后,客户端可以在一段时间内来重新获取服务端异步线程执行的结果。这些异步编程的使用场景都感觉很简单?但是异步编程应该不止于此,否则“加多”大佬也不会写出如此一部技术书籍,感觉是自己“菜是原罪”。

    实际工作中,关于异步编程有疑惑吗?

    有,必须有。还真是巧了,这段时间关于异步编程的使用场景,我还真的遇到了一个令我困惑的地方。大概的场景描述如下:

    • 客户端A在调用服务端B的一个接口1,服务端B的接口完成了第一步处理逻辑之后,已经得到了客户端A想要的结果。按照正常来说,这个时候可以返回结果。然后客户端A会接着请求服务端C的接口2。
    • 但是服务端B在处理完逻辑之后,还需要调用一个外部接口2,并且将结果插入数据库。
    • 服务端C在处理请求的过程中,最好是让其使用到刚刚插入数据库中的数据。

    看起来,这就是一个典型的异步编程技术使用的场景吧?

    但是,服务端B在处理完逻辑之后,调用一个外部接口2如果采用了异步方式。会存在当客户端后续请求服务端C的时候,这个外部接口返回的数据还没有被写入数据库,导致预期数据丢失

    如果采用同步方式调用外部接口2呢?会存在客户端请求服务端B接口超时问题(因为额外同步调用外部接口超时导致)。

    如果外部接口的调用刚好还是有代价的,比如是收费接口,那么我们通过收费接口获取的数据在异步方式中可能就没有被使用到,亏了;如果是同步方式,接口超时又影响了用户体验,还是不好。

    希望在看了加多老师的新书《Java异步编程实战》之后,我可以从中找到一些解决思路。

    Java异步编程实战》有哪些干货?

    Java异步编程实战》一书对Java中相关的异步编程技术进行归纳总结,为读者提供一个统一文档来查阅、参考。该书也是国内首本异步编程著作,作者全面解析Java异步编程,针对各种常见异步编程场景,从编程语言、开发框架等角度深入讲解了异步编程的原理和方法。

    看了这本书的目录和介绍,应该挺适合我们想要深入研究Java异步编程相关技术的同学。希望大家可以从《Java异步编程实战》一书中得到一些Java异步编程方面的启发,对Java异步编程技术有了一个更好的理解与掌握(期待.jpg)。

    最后,我给大家一个便捷购买入口吧,新书首发,大家敬请期待吧~

    购买链接:https://item.jd.com/12778422.html 在这里插入图片描述

    展开全文
  • Java异步编程最佳实践_.docx
  • Java异步编程简单真实案例

    千次阅读 2018-06-06 01:15:31
    由此我决定采用异步编程的方式来解决,除了让当前主线程用于执行主流程(比如构建视频文件基本视频至数据库),另外开启一个线程独立于主线程,在这里这个线程主要用于上传文件( 并且会将它的状态通知调用线程:成功,处理...
  • 新书推荐《Java异步编程实战》长按二维码了解及购买淘宝资深Java工程师撰写,从语言、框架等角度深入讲解异步编程原理和方法,周志明、李运华等推荐。编辑推荐作者是淘宝的资深Java技术专...
  • 认识Java异步编程

    2019-12-25 13:38:59
    一 、认识异步编程 通常Java开发人员喜欢使用同步代码编写程序,因为这种请求(request)/响应(response)的方式比较简单,并且比较符合编程人员的思维习惯;这种做法很好,直到系统出现性能瓶颈;在同步编程方式时由于...
  • Java中使用异步编程

    千次阅读 2021-03-07 00:25:15
    点击↑上方↑蓝色“编了个程”关注我~每周至少一篇原创文章这是本公众号的第 42 篇原创文章最近在学习协程,打算输出几篇文章来介绍一下协程。而协程与异步有很大的关联,所以想先介绍一下异步。异...
  • Java异步编程该从何谈起 标签(空格分隔): Java 从开始接触编程,我们早已悉知并熟练运用同步编程的思想。代码一行一行地写,一行一行地读,一行一行地调试。 在很长一段时间里,都没能认清楚异步编程的存在,...
  • Java 异步编程实战(上篇)

    千次阅读 2019-08-09 23:30:36
    异步编程是可以让程序并行运行的一种手段,其可以...本 Chat 旨在研究 Java异步编程的实践,从最简单的每当需要异步执行时候开启一个线程来执行,到使用线程池来复用线程执行异步任务,到使用可以产生异步执行结...
  • 本书涵盖了Java中常见的异步编程场景,包括单JVM内的异步编程、跨主机通过网络通信的远程过程调用的异步调用与异步处理,以及Web请求的异步处理等。在讲解Java中每种异步编程技术时都附有案例,以理论与实践相结合的...
  • Rx Java 异步编程框架

    2020-12-24 16:16:38
    Rx Java 文章目录Rx Java名词定义举个例子基本概念BackpressureUpstream, DownstreamObjects in motionAssembly timeSubscription timeRuntime特性Simple background computationSchedulersConcurrency within a ...
  • 这段时间的工作,使我意识到异步编程是工作中不可少的技能。异步编程分为两种:JVM内部的异步编程和JVM之间的异步编程。本文主要思考JVM内部的异步(简称为异步任务)。JVM之间的异步可以通过MQ等方式实现。  1、...
  • 文章目录一个例子回顾 Future一个例子走进CompletableFutureCompletableFuture使用场景创建异步任务supplyAsync方法runAsync方法任务异步回调1. thenRun/thenRunAsync2.thenAccept/thenAcceptAsync3. thenApply/...
  • 异步编程提供了一个非阻塞的,事件驱动的编程模型。 这种编程模型利用系统中多核执行任务来提供并行,因此提供了应用的吞吐率。此处吞吐率是指在单位时间内所做任务的数量。 在这种编程方式下, 一个工作单元将独立...
  • 异步编程提供了一个非阻塞的,事件驱动的编程模型。 这种编程模型利用系统中多核执行任务来提供并行,因此提供了应用的吞吐率。此处吞吐率是指在单位时间内所做任务的数量。 在这种编程方式下, 一个工作单元将独立...
  • 基于Java的轻量级异步编程框架

    千次阅读 2021-02-12 10:08:29
    # **DActor**## IntroductionDActor框架可同时支持同步和异步代码,简化在线异步代码的开发,用同步代码的思维来开发异步代码,兼顾异步代码的高并发、无阻塞和同步代码的易读性,可维护性。基于协程思想设计最大...
  • java nio 异步编程源码

    2017-01-05 17:42:23
    java bio nio aio socket
  • 异步java编程

    2018-08-15 15:01:12
    java异步编程,通过使用rxjava让多线程并发变的更加容易上手。
  • 异步编程原理以及Java实现

    万次阅读 多人点赞 2019-03-30 14:16:11
    说实话多线程和异步是非常容易让人混淆的,好像产生的效果差不多,甚至有人称多线程为异步,实际上这两种技术背后的实现原理是不同的。 假设您有2个任务,不涉及任何IO(在多处理器机器上)。在这种情况下,线程...
  • Java异步处理简单实践

    2020-12-22 16:59:43
    同步与异步  通常同步意味着一个任务的某个处理过程会对多个线程在用串行化处理,而异步则意味着某个处理过程可以允许多个线程同时处理。  异步通常代表着更好的性能,因为它很大程度上依赖于缓冲,是典型的使用...
  • DougLee可扩展的网络服务事件驱动Reactor模式基础版多线程版其他变体java.io包中分阻塞IOAPI一览Web服务器,分布式对象系统等等它们的共同特点Read请求解码请求报文业务处理编码响应报文发送响应实际应用中每一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 174,437
精华内容 69,774
关键字:

java异步编程

java 订阅
友情链接: 8 bit registers.rar