Jsf 在JavaEE中手动启动新线程安全吗?
对于在会话范围内的JSF托管bean中生成线程是否安全,我找不到明确的答案。线程需要调用无状态EJB实例(被注入托管bean的依赖项)上的方法 背景是我们有一份需要很长时间才能生成的报告。由于无法更改服务器设置,这导致HTTP请求超时。因此,我们的想法是启动一个新线程,让它生成报告并临时存储它。与此同时,JSF页面显示一个进度条,轮询托管bean直到生成完成,然后发出第二个请求下载存储的报告。这似乎有效,但我想确定我所做的不是黑客行为。简介 从会话范围内的托管bean中生成线程并不一定是一种黑客行为,只要它完成了您想要的任务。但是在其自身产生线程时需要非常小心。代码的编写方式不应使单个用户可以在每个会话中生成无限数量的线程,和/或即使在会话被破坏后线程仍继续运行。它迟早会炸毁你的应用程序 代码的编写方式需要确保用户在每个会话中不会产生超过一个后台线程,并且在会话被破坏时保证线程被中断。对于会话中的多个任务,您需要对任务进行排队。 此外,所有这些线程最好都由一个公共线程池提供服务,这样您就可以在应用程序级别限制生成线程的总数 因此,管理线程是一项非常微妙的任务。这就是为什么您最好使用内置的工具,而不是使用Jsf 在JavaEE中手动启动新线程安全吗?,jsf,jakarta-ee,concurrency,ejb,Jsf,Jakarta Ee,Concurrency,Ejb,对于在会话范围内的JSF托管bean中生成线程是否安全,我找不到明确的答案。线程需要调用无状态EJB实例(被注入托管bean的依赖项)上的方法 背景是我们有一份需要很长时间才能生成的报告。由于无法更改服务器设置,这导致HTTP请求超时。因此,我们的想法是启动一个新线程,让它生成报告并临时存储它。与此同时,JSF页面显示一个进度条,轮询托管bean直到生成完成,然后发出第二个请求下载存储的报告。这似乎有效,但我想确定我所做的不是黑客行为。简介 从会话范围内的托管bean中生成线程并不一定是一种黑客
new Thread()
和朋友在家中自行开发。一般的javaee应用服务器提供了一个容器管理的线程池,您可以通过EJB和其他工具来使用它。为了独立于容器(阅读:Tomcat-friendly),您还可以使用Java1.5的Util-Concurrent和
下面的示例假设JavaEE6+使用EJB
在表单提交中触发并忘记任务
在页面加载时异步获取模型
在后台持续更新应用程序范围的模型
@Named
@RequestScope//或@ViewScope
公共类Bean{
@EJB
私人SomeTop100Manager SomeTop100Manager;
公共列表getSomeTop100(){
返回someTop100Manager.list();
}
}
@Singleton
@并发管理(BEAN)
公共类SomeTop100Manager{
@持久上下文
私人实体管理者实体管理者;
私人名单前100名;
@施工后
@时间表(hour=“*”,minute=“*/1”,second=“0”,persistent=false)
公共空荷载(){
top100=实体管理器
.createNamedQuery(“Some.top100”,Some.class)
.getResultList();
}
公开名单(){
返回前100名;
}
}
另见:
@异步方法
。这正是他们的目的
使用OpenEJB 4.0.0-SNAPSHOTs的小示例。这里我们有一个@Singleton
bean,其中一个方法标记为@Asynchronous
。每次任何人调用该方法时,在本例中是JSF托管bean,无论该方法实际需要多长时间,它都会立即返回
@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);
}
}
}
在封面下,这项工作的关键在于:
- 调用者看到的
实际上不是JobProcessor
的实例。相反,它是一个覆盖了所有方法的子类或代理。应该是异步的方法以不同的方式处理JobProcessor
- 对异步方法的调用只会导致创建一个
,它封装了您提供的方法和参数。这个runnable被赋予一个线程池,它只是一个附加到线程池的工作队列Runnable
- 将工作添加到队列后,该方法的代理版本返回一个
的实现,该实现链接到当前正在队列上等待的Future
Runnable
- 当
最终在realRunnable
实例上执行该方法时,它将获取返回值并将其设置到JobProcessor
中,使其可供调用方使用Future
JobProcessor
返回的AsyncResult
对象与调用方持有的Future
对象不同。如果真正的JobProcessor
可以只返回String
,调用方版本的JobProcessor
可以返回Future
,那就太好了,但我们没有看到任何不增加复杂性的方法。因此,AsyncResult
是一个简单的包装器对象。容器将拉出字符串
,扔掉异步结果
,然后将字符串
放在调用方持有的真实未来
中
要在这一过程中取得进展,只需将一个线程安全的对象(如
@Asynchronous
方法)传递给bean,并让bean代码以完成百分比定期更新它。我尝试了这个方法,在我的JSF托管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();
}
完全同意繁殖线程是可以的,只要它做得非常小心(完美的措辞)。注意:我们最终在EJB3.1的规范级别解决了这一需求。请参阅我的@Asynchronous答案。@Asynchronous确实是EJB3.1中最好的补充之一。我希望EJB3.2/JavaEE7会考虑JDK7中fork/join的托管变体。@BalusC您能否详细介绍一些工具/功能,以便了解会话何时被破坏并结束线程(在EJB3.0设置中)。如果需要的话,我可以创建一个新的问题fyi:javaee7,然后使用.Nice comment I获得新的线程特性
@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);
}
}
@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;
}
}
@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);
}
}
}
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);
}
}
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();
}