介绍
Java EE具有许多API和构造以支持异步执行。 从可伸缩性和性能的角度来看,这是至关重要的。
让我们假设2个模块相互交互。 当模块A (发送方)以同步方式向模块B (接收方)发送消息时,通信将在单个线程的上下文中进行,即,从模块A发起通信的线程将被阻塞,直到模块B作出响应为止。
这是一个通用的声明,但可以扩展到一个简单的Java方法相互交融的背景下-在这种情况下,从了methodA到的methodB 同步调用将在同一个 线程执行这将被阻塞,直到的methodB返回或抛出一个异常。
为什么在基于Java EE的应用程序中需要异步行为?
我们可以进一步将这一点推算到Java EE领域-服务器之间的通信,例如Web层和EJB层(Servlet和EJB)之间的通信,或者是典型的客户端服务器交互-浏览器与RESTful端点,Servlet等的交互-服务器响应客户端请求的线程始终阻塞,直到服务器组件响应。
异步执行在这里发挥作用-如果可以释放/暂停处理客户请求的服务器线程 ,并且在单独的线程中执行实际的业务逻辑(与原始线程不同),则可以极大地提高性能和可伸缩性。 ! 例如,如果分配给侦听客户端请求的HTTP侦听器线程被立即释放,则可以自由地处理来自其他客户端的请求,并且可以在单独的容器线程中执行业务逻辑,然后该容器线程可以通过适当的方法(例如, java.util.concurrent.Future对象或通过客户端注册的回调处理程序 。 从最终用户的角度考虑- 响应非常重要!
深入研究:Java EE中的异步构造和API
让我们看一下Java EE中的一些异步相关功能(API)。 这不是一个详尽的列表,但是应该是一个很好的起点。
不同的Java EE规范具有促进异步功能的典型方式和API。 让我们探索以下Java EE规范
- JAX-RS 2.0(Java EE 7)
- Websocket 1.0(Java EE 7)
- 并发实用工具1.0(Java EE 7)
- EJB 3.1(Java EE 6)
- Servlet 3.0(Java EE 6)
注意 :下面提供的代码为摘要形式(出于明显的原因)。 完整的样本可以在这里访问 。
JAX-RS 2.0
请求的异步处理是JAX-RS 2.0版 (Java EE 7中的新增功能 )的一项新功能 。 为了使用JAX-RS API执行aysnc请求,需要在JAX-RS资源方法本身中注入对javax.ws.rs.container.AsyncResponse接口的引用。 此参数将请求执行置于异步模式,然后该方法继续执行。 业务逻辑执行完成后,需要从单独的线程中调用AsynResponse对象上的resume方法。 可以利用Java EE并发实用程序功能(稍后讨论),例如javax.enterprise.concurrent.ManagedExecutorService ,以将业务逻辑封装为Runnable对象,并将其提交给容器的执行者服务,其余部分由该服务执行。 无需自己产生不受管理的隔离线程。
@Path("/{id}")@GET@Produces("application/xml")public void asyncMethod(@Suspended AsyncResponse resp, @PathParam("id") String input) {System.out.println("Entered MyAsyncRESTResource/asyncMethod() executing in thread: "+ Thread.currentThread().getName());mes.execute(() -> {System.out.println("Entered Async zone executing in thread: "+ Thread.currentThread().getName());System.out.println("Simulating long running op via Thread sleep() started on "+ new Date().toString());try {Thread.sleep(5000);} catch (InterruptedException ex) {Logger.getLogger(MyAsyncRESTResource.class.getName()).log(Level.SEVERE, null, ex);}System.out.println("Completed Long running op on "+new Date().toString());System.out.println("Exiting Async zone executing in thread: "+ Thread.currentThread().getName());//creating a dummy instance of our model class (Student)Student stud = new Student(input, "Abhishek", "Apr-08-1987");resp.resume(Response.ok(stud).build());});System.out.println("Exit MyAsyncRESTResource/asyncMethod() and returned thread "+Thread.currentThread().getName()+" back to thread pool");}
JAX-RS客户端API也具有异步功能,但本文未对此进行讨论。 他们绝对值得一看!
Websocket 1.0
Websocket API是Java EE工具库(Java EE 7中引入)的全新添加 。 它促进了双向 (服务器和客户端发起的)通信的本质上也是全双工的(客户端或服务器都可以在任何时候相互发送消息)。
为了使用Websocket API发送异步消息,需要使用javax.websocket.Session接口上可用的getAsyncRemote方法。 在内部,这不过是javax.websocket.RemoteEnpoint – javax.websocket.RemoteEnpoint.Async的嵌套接口的实例。 对此调用常规的sendXXX方法将导致发送过程在单独的线程中执行。 当您考虑交换大消息或处理需要向其发送消息的大量Websocket客户端时,这特别有用。 枯萎的方法返回一个java.util.concurrent.Future对象,或者可以以javax.websocket.SendHandler接口实现的形式注册一个回调。
public void sendMsg(@Observes Stock stock) {System.out.println("Message receieved by MessageObserver --> "+ stock);System.out.println("peers.size() --> "+ peers.size());peers.stream().forEach((aPeer) -> {//stock.setPrice();aPeer.getAsyncRemote().sendText(stock.toString(), (result) -> {System.out.println("Message Sent? " + result.isOK());System.out.println("Thread : " + Thread.currentThread().getName());});});}
并发实用工具1.0
Java EE并发实用程序是Java EE 7的另一个重要补充 。 它提供了一种标准的生成线程的方式–好的方面是,这些线程是容器管理的 , 而不仅仅是容器没有上下文信息的孤立/孤立线程 。
通常,并发实用工具1.0提供了一些用于在单独线程中执行异步任务的标准构造。 它们如下: javax.enterprise.concurrent.ManagedExecutorService 和 javax.enterprise.concurrent.ManagedScheduledExecutorService 。
为了在单独的线程中启动新任务,可以使用ManagedExecutorService接口提交Runnable 。 除了实现Runnable接口之外,类还可以实现javax.enterprise.concurrent.ManagedTask接口并提供javax.enterprise.concurrent.ManagedTaskListener实现,以便侦听通过ManagedExecutorService提交的任务的生命周期更改。
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {System.out.println("Enter AConcurrencyUtilsExample/doGet executing in thread "+ Thread.currentThread().getName());System.out.println("initiating task . . . ");mes.execute(new AManagedTask());System.out.println("Exit AConcurrencyUtilsExample/doGet and returning thread "+ Thread.currentThread().getName() +" back to pool");}
Servlet 3.0
Servlet 3.0( Java EE 6的一部分)中引入了异步HTTP,它基本上提供了在单独的线程中执行请求并挂起处理客户端调用的原始线程的功能。
这里的关键角色是javax.servlet.AsyncContext接口。 为了启动异步处理,调用java.servlet.ServletRequest接口的startAsync方法。 为了执行核心逻辑,需要将java.lang.Runnable对象提交给AsyncContext接口的start方法。 可以选择通过实现javax.servlet.AsyncListener来附加侦听器,以便在Async任务执行的特定时间接收回调通知。
@Overridepublic void doGet(HttpServletRequest req, HttpServletResponse resp) {PrintWriter writer = null;try {writer = resp.getWriter();} catch (IOException ex) {Logger.getLogger(MyAsyncServlet.class.getName()).log(Level.SEVERE, null, ex);}//System.out.println("entered doGet()");writer.println("ENTERING ... " + MyAsyncServlet.class.getSimpleName() + "/doGet()");writer.println("Executing in Thread: " + Thread.currentThread().getName());//step 1final AsyncContext asyncContext = req.startAsync();//step 2asyncContext.addListener(new CustomAsyncHandler(asyncContext));//step 3asyncContext.start(() -> {PrintWriter logger = null;try {logger = asyncContext.getResponse().getWriter();} catch (IOException ex) {Logger.getLogger(MyAsyncServlet.class.getName()).log(Level.SEVERE, null, ex);}logger.println("Long running Aync task execution started : " + new Date().toString());logger.println("Executing in Thread: " + Thread.currentThread().getName());try {Thread.sleep(5000);} catch (InterruptedException e) {Logger.getLogger(MyAsyncServlet.class.getName()).log(Level.SEVERE, null, e);}logger.println("Long task execution complete : " + new Date().toString());logger.println("Calling complete() on AsyncContext");//step 4asyncContext.complete();});writer.println("EXITING ... " + MyAsyncServlet.class.getSimpleName() + "/doGet() and returning initial thread back to the thread pool");}
EJB 3.1
通常,(在EJB 3.1之前)EJB消息驱动Bean用于满足与异步相关的要求。 MDB bean侦听发送到javax.jms.Destination ( 队列或Topic )的消息,并执行所需的业务逻辑–从发送电子邮件到启动订单处理任务,这可能是任何事情。 要了解的重要一点是,首先将消息发送到Queue的客户端不知道 MDB(已解耦 ),并且不必等待/保持阻塞,直到作业结束(电子邮件收据或订单处理确认) )。
EJB 3.1( Java EE 6的一部分)引入了javax.ejb.Asynchronous批注。 可以将其放在EJB会话bean(无状态,有状态或单例) 类 (使所有方法异步)上,也可以放在方法级别本身上(以防需要精细控制)。 如果需要跟踪异步方法的结果,则带有@Asynchronous批注的方法可以返回void (失火并忘记)或java.util.concurrent.Future的实例–这可以通过调用Future.get()来完成–需要注意的是, get方法本身实际上是阻塞的。
@Asynchronouspublic Future<String> asyncEJB2(){System.out.println("Entered MyAsyncEJB/asyncEJB2()");System.out.println("MyAsyncEJB/asyncEJB2() Executing in thread: "+ Thread.currentThread().getName());System.out.println("Pretending as if MyAsyncEJB/asyncEJB2() is doing something !");try {Thread.sleep(5000);} catch (InterruptedException ex) {java.util.logging.Logger.getLogger(MyAsyncEJB.class.getName()).log(Level.SEVERE, null, ex);}System.out.println("Exiting MyAsyncEJB/asyncEJB2()");return new AsyncResult("Finished Executing on "+ new Date().toString());}
这是Java EE功能的相当简短的预览。 这些API和规范功能丰富,很难通过博客文章涵盖所有这些API和规范! 我希望这能激起您的兴趣,并为您提供进一步探索的起点。
干杯!
翻译自: https://www.javacodegeeks.com/2014/08/java-ee-asynchronous-constructs-and-capabilities.html