我无法找到一个明确的答案来确认在会话范围内JSF托管bean中生成线程是否安全。该线程需要调用无状态EJB实例上的方法(该实例被依赖注入到管理的Bean中)。
背景是我们有一个需要长时间生成的报告,由于我们无法更改服务器设置,这导致HTTP请求超时。因此,想法是启动一个新线程让它生成并且在临时存储中保存报告。同时,JSF页面显示进度条,轮询管理的bean直到生成完成,然后发出第二个请求来下载存储的报告。这似乎可以工作,但我想确保我的操作不是一种欺骗行为。
我无法找到一个明确的答案来确认在会话范围内JSF托管bean中生成线程是否安全。该线程需要调用无状态EJB实例上的方法(该实例被依赖注入到管理的Bean中)。
背景是我们有一个需要长时间生成的报告,由于我们无法更改服务器设置,这导致HTTP请求超时。因此,想法是启动一个新线程让它生成并且在临时存储中保存报告。同时,JSF页面显示进度条,轮询管理的bean直到生成完成,然后发出第二个请求来下载存储的报告。这似乎可以工作,但我想确保我的操作不是一种欺骗行为。
请查看 EJB 3.1 的 @Asynchronous methods
。这正是它们的用途。
以下是使用 OpenEJB 4.0.0-SNAPSHOTs 的简单示例。这里有一个标记为@Asynchronous
的方法的@Singleton
bean。每当任何人(在本例中为您的 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);
}
}
}
这里有一个小测试用例,连续调用了那个@Asynchronous
方法几次。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
实例,而是一个子类或代理,拥有所有方法被覆盖。应该异步执行的方法会以不同的方式处理。Runnable
。这个Runnable
会传递给一个Executor,它只是一个连接到线程池的工作队列。Runnable
相关联的Future
的实现。Runnable
最终在真正的JobProcessor
实例上执行方法时,它将获取返回值并将其设置到Future
中,以便调用者可以访问。重要的是要注意JobProcessor
返回的AsyncResult
对象并不是调用者持有的相同Future
对象。如果真正的JobProcessor
可以返回String
,并且调用者版本的JobProcessor
可以返回Future<String>
,那就太好了,但我们没有找到不添加更多复杂性的方式。因此,AsyncResult
是一个简单的包装对象。容器将获取String
,丢弃AsyncResult
,然后将String
放入调用者持有的真正的Future
中。
要获得进度更新,只需将线程安全对象(如AtomicInteger)传递给@Asynchronous
方法,并让bean代码定期更新它的完成百分比。
10
。 - Arjan TijmsExecutorService
上的public <T> Future<T> submit(Callable<T> task)
驱动的,它使用FutureTask
作为实现。Future.cancel()
方法的契约略有不同,因此容器也会用实现了额外部分并最终委托给FutureTask
的Future
对象来包装该对象。 - David Blevins在会话范围的托管 bean 中生成线程不一定是一种黑客行为,只要它完成您想要的工作即可。但是自行生成线程需要极其小心。代码不应该编写成这样,即单个用户可以例如在会话中生成无限数量的线程和/或线程即使在会话结束后仍然继续运行。这早晚会导致应用程序崩溃。
代码需要这样编写,以确保例如一个用户永远不能在会话中生成多个后台线程,并且在会话结束时保证可以中断该线程。对于会话中的多个任务,您需要将任务排队。另外,所有这些线程最好由公共线程池提供服务,以便您可以在应用程序级别上限制生成的线程总数。
管理线程是非常微妙的任务,因此最好使用内置工具而不是自己使用new Thread()
等方式创建。平均Java EE应用程序服务器提供了一个容器管理的线程池,您可以通过EJB的@Asynchronous
和@Schedule
等方式来利用它。为了独立于容器(即Tomcat友好),您还可以使用Java 1.5的Util Concurrent ExecutorService
和ScheduledExecutorService
。@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() {
// ...
}
}
@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);
}
}
@Eager
注释托管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管理的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();
}