大家好!
在这里,我们
沉迷于包子,并启动了
“ Java Enterprise Developer”课程的第二流。 该课程的永久创建者和老师
-Vitaly Ivanov甚至撰写了有关该主题的文章,我们希望这对您似乎很有用:)
所以我们走吧:)
本文探讨了JavaEE并发性规范API(
JSR 236 ),该API使用托管资源的概念定义了JavaEE容器中并行任务的标准。 第七版JavaEE的发布使在Enterprise容器中运行并行任务成为可能,从而为开发人员提供了用于处理多任务的便捷工具和实用程序。 直到那一刻,所有的多任务处理都留给了所用应用服务器的特定实现,该服务器独立决定任务的优化。 在构建企业应用程序的体系结构时,违反此原则被认为是不好的做法。 结果,不建议开发人员创建新线程,有时在容器级别禁止这种行为。
企业bean不得尝试管理线程。 企业bean不得尝试启动,停止,挂起或恢复线程,也不能尝试更改线程的优先级或名称。 企业bean不得尝试管理线程组。(作者的免费翻译:EJB不应尝试管理线程,即尝试启动,停止,暂停和恢复其执行,或者更改优先级或更改线程的名称。而且,EJB不应尝试管理线程组)。实际上,禁止在JavaEE容器中创建自己的线程是很成问题的,但是,使用这种方法,容器的后台服务无法保证其工作的正确性。 例如,如果使用JavaSE的线程继承者(或Runnable实现)在新线程中启动任务,则在EJB方法完成时关闭事务可能会无法正常工作。 同样,使用通过Executors类的静态方法创建的Executor API提供的基本接口类型(例如ExecutorService和ScheduledExecutorService)会导致潜在的错误并中断容器服务的执行。
在JavaEE规范推荐的用于异步执行任务的工具中,开发人员必须使用异步的Stateless / Statefull EJB和/或Message Driven Bean,它们的功能足以满足一定范围的任务,最重要的是,其管理最初完全由应用程序服务器完全控制,即EJB容器。
但是,如前所述,由于
JSR 236 ,容器管理的资源出现了,该资源实现了对多线程和异步任务执行的支持,从而扩展了JavaSE
java.util.concurrent
包的功能。 对于JavaEE堆栈,托管资源类位于
javax.enterprise.concurrent
包中,并通过使用
@Resource
批注的资源注入或通过JNDI上下文(尤其是InitialContext)来提供对这些类的对象的访问。 同时,添加了使用JavaEE应用程序内部多线程环境所熟悉的Future / ScheduledFuture / CompletableFuture对象的可能性。
因此,有足够的歌词,让我们从实际的角度(即在应用程序代码中使用应用程序的上下文中)以及从以Glassfish 5应用程序服务器为例的资源配置的角度,仔细查看规范提供的每个托管资源。
嗯,ManagedExecutorService类是第一个要考虑的类,(从名称中已经了解)该类扩展了熟悉的JavaSE的功能,即开箱即用的ExecutorService,并设计用于JavaEE环境中的异步执行任务。
要不仅在Glassfish应用程序服务器中配置这种类型的ExecutorService,还应该引用domain.xml配置文件,该文件的位置由$ {GLASSFISH_HOME} / domains / <domainname> / config目录确定。 该文件的片段如下所示:
<domain application-root="${com.sun.aas.instanceRoot}/applications" version="25" log-root="${com.sun.aas.instanceRoot}/logs"> <resources> <context-service object-type="system-all" jndi-name="concurrent/__defaultContextService" /> <managed-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedExecutorService" /> <managed-scheduled-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedScheduledExecutorService" /> <managed-thread-factory object-type="system-all" jndi-name="concurrent/__defaultManagedThreadFactory" /> </resources> <servers> <server config-ref="server-config" name="server"> <resource-ref ref="concurrent/__defaultContextService" /> <resource-ref ref="concurrent/__defaultManagedExecutorService" /> <resource-ref ref="concurrent/__defaultManagedScheduledExecutorService" /> <resource-ref ref="concurrent/__defaultManagedThreadFactory" /> </server> </servers> </domain>
进入Glassfish 5管理面板的界面,进行配置
ManagedExecutorService如下:

本节允许创建相同类型的新资源,现有资源的管理,删除以及锁定和解锁。
对于Glassfish中的控制台管理迷来说,展示了一个功能强大的asadmin实用程序,在其中使用
create-managed-executor-service
命令,可以创建新的ManagedExecutorService资源:

在应用程序代码中,使用资源注入来获取对ManagedExecutorService创建的对象的引用更为方便,但是您也可以使用JNDI工具,如下所示:
@Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; InitialContext context = new InitialContext(); ManagedExecutorService managedExecutorServiceWithContext = (ManagedExecutorService) context.lookup( "concurrent/OtusExecutorService");
我想引起读者的注意的是,对于
@Resource
批注,lookup参数是可选的,并且如果开发人员未在应用程序代码中定义它,则容器将在其
__default
中注入
__default
前缀的默认资源。 在这种情况下,对于开发人员来说,代码变得更加简洁:
@Resource ManagedExecutorService executor;
在使用
execute()
和
submit()
方法接收到对此对象的引用之后,您可以运行在容器内实现Runnable或Callable接口的任务。
以一个示例为例,我想指出,在所有可能的情况中,最有趣的是在分布式JavaEE环境中执行的任务,在该任务中,在多线程环境中提供事务支持很重要。 如您所知,JavaEE已经开发了JTA(Java Transaction API)规范,该规范允许您通过以
begin()
方法显式启动它并以
commit()
方法,提交更改或
rollback()
作为显式开始来确定事务的边界,从而回滚已执行的动作。
考虑一个示例任务,该任务从用户事务中的索引按百项列表返回一条消息:
public class TransactionSupportCallableTask implements Callable<String> { private int messageIndex; public TransactionSupportCallableTask(int messageId) { this. messageIndex = messageId; } public String call() { UserTransaction tx = lookupUserTransaction(); String message = null; try { tx.begin(); message = getMessage(messageIndex); tx.commit(); } catch (Exception e) { e.printStackTrace(); try { tx.rollback(); } catch (Exception e1) { e1.printStackTrace(); } } return message; } private void getMessage(int index) { … } private UserTransaction lookupUserTransaction () { … } }
Servlet的代码,该代码在随机选择的索引处显示列表中的消息:
@WebServlet("/task") public class ManagedExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Future<String> futureResult = executor.submit(new TransactionSupportCallableTask(Random.nextInt(100))); while (!futureResult.isDone()) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } try { response.getWriter().write("Callable task has received message with following content '" + futureResult.get() + "'"); } catch(Exception e) { e.printStackTrace(); } } }
接下来要考虑的是ManagedScheduledExecutorService资源,其主要目的是计划以固定间隔重复执行或需要延迟执行的任务。

从通过GlassFish管理控制台配置此资源的角度来看,与以前的类型相比,没有发现特别的更改:

为了快速创建ManagedScheduledExecutorService类型的资源,
asadmin
使用
create-managed-scheduled-executor-service
命令

在应用程序代码中,我们仍然使用资源注入:
@Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor;
用于执行此类ExecutorService的任务的主要方法是:
schedule()
(用于接收输入为Runnable或Callable类型的任务
scheduleAtFixedRate()
和
scheduleAtFixedRate()
(用于确定任务的初始延迟,并在TimeUnit中设置重复间隔(秒,分钟等)。 )。
前面的情况可以重写如下:
@WebServlet("/scheduledTask") public class ManagedScheduledExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ScheduledFuture<String> futureResult = scheduledExecutor.schedule( new TransactionSupportCallableTask(Random.nextInt(100)), 5, TimeUnit.SECONDS); while (!futureResult.isDone()) { try { Thread.sleep(50);
此外,企业版的并发API环境还提供了创建受控流的功能。 对于这些任务,您应该使用托管线程工厂的功能,该功能通过同名的ManagedThreadFactory类实现其功能,并且也可以通过JNDI服务进行访问:
@Resource ManagedThreadFactory factory;
Glassfish控制台的管理窗口看起来“过时”:

使用托管线程工厂,不仅可以为容器提供流控制机制,还可以初始化生成的线程的属性:设置名称和优先级,这在将来可以通过轻松检测先前命名的线程的执行顺序来极大地简化解析线程转储时对问题的搜索。
在我们的例子中,我们定义了流的类,该流显示了与该任务紧密联系在一起的朋友的信息:
public class SimpleThreadTask implements Runnable { private String friend; public SimpleThreadTask(String friend){ this.friend = friend; } @Override public void run() { System.out.println("Hello, " + friend); } }
让servlet启动线程并将其报告给输出:
@WebServlet("/thread") public class ManagedThreadFactoryServlet extends HttpServlet { @Resource ManagedThreadFactory factory; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Thread thread = factory.newThread(new SimpleThreadTask("Otus")); thread.setName("ManagedThreadFromPool"); thread.setPriority(7); thread.start(); response.getWriter().write("Custom thread has been running."); } }
转向JavaEE在多线程领域的最终功能-上下文服务,应注意,这些服务创建了动态上下文代理对象。 我们都熟悉JavaSE中的动态代理的功能(
java.lang.reflect.Proxy
),它使您能够生成所需接口的动态实现,其功能被活跃地用于创建数据库连接和事务管理,并用于各种AOP拦截器等。 此外,对于通过JavaEE上下文服务创建的代理,假定它们可以在公共JNDI上下文,安全性上下文和容器类的框架内工作。
要连接服务,只需使用以下代码:
@Resource ContextService service;
从管理和配置此资源的角度来看,一切都非常熟悉并且类似于已经考虑的类型:

以下是在容器上下文中启动代理任务的线程的示例:
public class SampleProxyTask implements Runnable { @Override public void run() {
用于创建上下文代理的无状态EJB bean:
@Stateless public class ContextServiceBean { @Resource ContextService service; @Resource ManagedExecutorService executor; public void perform(Runnable task) { Runnable proxy = service.createContextualProxy(task, Runnable.class); executor.submit(proxy); } }
最后,是执行任务的servlet的代码:
@WebServlet("/context") public class ContextServiceServlet extends HttpServlet { @Inject ContextServiceBean contextServiceBean; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { contextServiceBean.perform(new SampleProxyTask()); } }
这实际上终止了JavaEE开发人员在多线程环境中工作的能力。 多亏了他们,容器中发生的所有进程和服务都将在服务器的严格控制下,以协调其工作并且不违反通常的执行顺序。 为了达到企业开发人员的目标,这些功能通常就足够了,在第八版中,此API并未更改。
结束
与往常一样,我们正在等待问题和评论,一定要查看
Vitaly 的公开课程 ,在那里他还可以提出问题并收听/参与“ CDI in action @