在JSF托pipebean中启动新线程安全吗?

我无法find一个明确的答案,以确定是否在会话范围内的JSF托pipebean中产生线程是安全的。 线程需要调用无状态EJB实例上的方法(dependency injection到托pipebean)。

背景是,我们有一个需要很长时间才能生成的报告。 这导致HTTP请求超时,因为我们无法更改服务器设置。 所以这个想法是开始一个新的线程,让它生成报告并暂时存储它。 同时,JSF页面显示一个进度条,轮询托pipebean直到生成完成,然后再次请求下载存储的报告。 这似乎工作,但我想确定我在做什么不是黑客。

介绍

从会话范围的托pipebean中产生线程不一定是黑客,只要它做你想做的工作。 但是在自己需要的时候产生线程需要非常小心。 不应该以这种方式编写代码,即单个用户可以例如在每个会话中产生无限数量的线程和/或即使在会话被销毁之后线程也继续运行。 它迟早会炸掉你的应用程序。

代码需要以这种方式编写,以确保用户可以例如永远不会在每个会话中产生多于一个后台线程,并且只要会话被破坏,就保证线程被中断。 对于会话中的多个任务,您需要对任务进行排队。

另外,所有这些线程最好由一个公共线程池提供服务,以便在应用程序级别上限制衍生线程的总量。 普通的Java EE应用程序服务器提供了一个容器pipe理的线程池,您可以通过其中的EJB @Asynchronous@Schedule 。 为了独立于容器,您也可以使用Java 1.5的Util Concurrent ExecutorServiceScheduledExecutorService

以下示例假定使用EJB的Java EE 6+。

在表单提交时,点火并忘记任务

 @Named @RequestScoped // Or @ViewScoped public class Bean { @EJB private SomeService someService; public void submit() { someService.asyncTask(); // ... (this code will immediately continue without waiting) } } 
 @Stateless public class SomeService { @Asynchronous public void asyncTask() { // ... } } 

在页面加载时asynchronous获取模型

 @Named @RequestScoped // Or @ViewScoped public class Bean { private Future<List<Entity>> asyncEntities; @EJB private EntityService entityService; @PostConstruct public void init() { asyncEntities = entityService.asyncList(); // ... (this code will immediately continue without waiting) } public List<Entity> getEntities() { try { return asyncEntities.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new FacesException(e); } catch (ExecutionException e) { throw new FacesException(e); } } } 
 @Stateless public class EntityService { @PersistenceContext private EntityManager entityManager; @Asynchronous public Future<List<Entity>> asyncList() { List<Entity> entities = entityManager .createQuery("SELECT e FROM Entity e", Entity.class) .getResultList(); return new AsyncResult<>(entities); } } 

如果您使用的是JSF实用程序库OmniFaces ,如果使用@Eager注释托pipe的bean,则可以更快地完成此操作。

在应用程序启动时安排后台作业

 @Singleton public class BackgroundJobManager { @Schedule(hour="0", minute="0", second="0", persistent=false) public void someDailyJob() { // ... (runs every start of day) } @Schedule(hour="*/1", minute="0", second="0", persistent=false) public void someHourlyJob() { // ... (runs every hour of day) } @Schedule(hour="*", minute="*/15", second="0", persistent=false) public void someQuarterlyJob() { // ... (runs every 15th minute of hour) } @Schedule(hour="*", minute="*", second="*/30", persistent=false) public void someHalfminutelyJob() { // ... (runs every 30th second of minute) } } 

在后台不断更新应用程序范围的模型

 @Named @RequestScoped // Or @ViewScoped public class Bean { @EJB private SomeTop100Manager someTop100Manager; public List<Some> getSomeTop100() { return someTop100Manager.list(); } } 
 @Singleton @ConcurrencyManagement(BEAN) public class SomeTop100Manager { @PersistenceContext private EntityManager entityManager; private List<Some> top100; @PostConstruct @Schedule(hour="*", minute="*/1", second="0", persistent=false) public void load() { top100 = entityManager .createNamedQuery("Some.top100", Some.class) .getResultList(); } public List<Some> list() { return top100; } } 

也可以看看:

  • 产生JSF托pipebean中的线程以使用计时器进行计划任务

检查EJB 3.1 @Asynchronous methods 。 这正是他们的目标。

使用OpenEJB 4.0.0-SNAPSHOT的小例子。 这里我们有一个@Singleton bean,其中一个方法标记为@Asynchronous 。 每当这个方法被任何人调用,在这种情况下你的JSF托pipebean,它将立即返回,无论方法实际需要多长时间。

 @Singleton public class JobProcessor { @Asynchronous @Lock(READ) @AccessTimeout(-1) public Future<String> addJob(String jobName) { // Pretend this job takes a while doSomeHeavyLifting(); // Return our result return new AsyncResult<String>(jobName); } private void doSomeHeavyLifting() { try { Thread.sleep(SECONDS.toMillis(10)); } catch (InterruptedException e) { Thread.interrupted(); throw new IllegalStateException(e); } } } 

这里有一个testing用例,连续多次调用@Asynchronous方法。

每次调用都会返回一个Future对象,它实质上是空的 ,随后在相关的方法调用实际完成时,它的值将由容器填充。

 import javax.ejb.embeddable.EJBContainer; import javax.naming.Context; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class JobProcessorTest extends TestCase { public void test() throws Exception { final Context context = EJBContainer.createEJBContainer().getContext(); final JobProcessor processor = (JobProcessor) context.lookup("java:global/async-methods/JobProcessor"); final long start = System.nanoTime(); // Queue up a bunch of work final Future<String> red = processor.addJob("red"); final Future<String> orange = processor.addJob("orange"); final Future<String> yellow = processor.addJob("yellow"); final Future<String> green = processor.addJob("green"); final Future<String> blue = processor.addJob("blue"); final Future<String> violet = processor.addJob("violet"); // Wait for the result -- 1 minute worth of work assertEquals("blue", blue.get()); assertEquals("orange", orange.get()); assertEquals("green", green.get()); assertEquals("red", red.get()); assertEquals("yellow", yellow.get()); assertEquals("violet", violet.get()); // How long did it take? final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start); // Execution should be around 9 - 21 seconds assertTrue("" + total, total > 9); assertTrue("" + total, total < 21); } } 

示例源代码

下面是什么使这项工作是:

  • 调用者看到的JobProcessor实际上并不是JobProcessor的一个实例。 相反,它是一个覆盖所有方法的子类或代理。 应该是asynchronous的方法处理不同。
  • 调用asynchronous方法只会导致创build一个Runnable ,它包装了您提供的方法和参数。 这个runnable被赋予一个Executor ,它只是一个连接到线程池的工作队列。
  • 在将工作添加到队列之后,该方法的代理版本将返回一个Future的实现,该实现链接到正在等待队列的Runnable
  • Runnable最终在真实的 JobProcessor实例上执行该方法时,它将获取返回值并将其设置到Future ,使其可用于调用方。

重要的是要注意, JobProcessor返回的AsyncResult对象与调用者所持有的Future对象不同。 如果真正的JobProcessor只能返回String ,而调用者的JobProcessor的版本可能返回Future<String> ,那么这将是整洁的,但是我们没有看到任何方式去做,而不增加更多的复杂性。 所以AsyncResult是一个简单的包装对象。 容器会把String拉出来,抛出AsyncResult ,然后把String放在调用者所持的真正的 Future中。

为了获得进展,只需将像AtomicInteger这样的线程安全的对象传递给@Asynchronous方法,然后让bean代码以百分比完成的方式定期更新它。

我尝试了这一点,并从我的JSFpipe理bean中发挥出色

 ExecutorService executor = Executors.newFixedThreadPool(1); @EJB private IMaterialSvc materialSvc; private void updateMaterial(Material material, String status, Location position) { executor.execute(new Runnable() { public void run() { synchronized (position) { // TODO update material in audit? do we need materials in audit? int index = position.getMaterials().indexOf(material); Material m = materialSvc.getById(material.getId()); m.setStatus(status); m = materialSvc.update(m); if (index != -1) { position.getMaterials().set(index, m); } } } }); } @PreDestroy public void destory() { executor.shutdown(); }