在JSF托管bean中启动新线程是否安全?

Dmi*_*nyi 45 concurrency jsf ejb java-ee

我无法找到一个明确的答案,确定在会话范围内的JSF托管bean中生成线程是否安全.线程需要调用无状态EJB实例上的方法(依赖注入到托管bean).

背景是我们有一个需要很长时间才能生成的报告.这导致HTTP请求由于我们无法更改的服务器设置而超时.因此,我们的想法是启动一个新线程,让它生成报告并暂时存储它.与此同时,JSF页面显示了一个进度条,轮询托管bean直到生成完成,然后再发出第二个请求来下载存储的报告.这似乎有效,但我想确定我所做的不是黑客.

Dav*_*ins 52

查看EJB 3.1 @Asynchronous methods.这正是它们的用途.

使用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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

示例源代码

在幕后的内容是:

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

需要注意的是该AsyncResult对象的JobProcessor回报率是不一样的Future调用者保存对象.如果真实JobProcessor可以返回String并且调用者的版本JobProcessor可以返回Future<String>,那将是很好的,但是如果不增加更多的复杂性,我们没有看到任何方法.所以这AsyncResult是一个简单的包装器对象.容器将拉出String,扔掉AsyncResult,然后放入调用者持有String真实 Future.

要在此过程中取得进展,只需将类似AtomicInteger的线程安全对象传递给@Asynchronous方法,并让bean代码定期更新完成百分比.

  • 确实很好评论!我们最近开始从JSF支持bean调用@Asynchronous注释方法,结果非常好.如果代码能够尽早触发异步操作,则其执行可能与JSF执行的部分生命周期处理重叠.如果您密集使用此功能,则可以了解线程池的大小.在JBoss AS 6.0中,这似乎是"10". (2认同)
  • @mxrider您可以将OpenEJB作为war文件放入任何5.5或更高版本的Tomcat中,并获得所有这些.另请查看Apache TomEE http://openejb.apache.org/3.0/apache-tomee.html (2认同)

Bal*_*usC 43

介绍

只要它能完成您想要的工作,就可以在会话范围内的托管bean中生成线程.但产生线程本身需要非常谨慎地完成.代码不应该以单个用户可以为每个会话产生无限量的线程和/或即使在会话被销毁之后线程继续运行的方式编写.它迟早会炸毁你的申请.

代码需要以这样的方式编写,即您可以确保用户可以例如从不为每个会话生成多个后台线程,并且保证线程在会话被销毁时被中断.对于会话中的多个任务,您需要对任务进行排队.

此外,所有这些线程最好应由公共线程池提供服务,以便您可以在应用程序级别对生成的线程总量进行限制.普通的Java EE应用程序服务器提供了一个容器管理的线程池,您可以通过其中的EJB new Thread()@Asynchronous.是独立的容器,你也可以使用Java 1.5的并发的Util @ScheduleExecutorService这一点.

下面的示例假设Java EE 6+与EJB.

在表单提交时触发并忘记任务

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    @EJB
    private SomeService someService;

    public void submit() {
        someService.asyncTask();
        // ... (this code will immediately continue without waiting)
    }

}
Run Code Online (Sandbox Code Playgroud)
@Stateless
public class SomeService {

    @Asynchronous
    public void asyncTask() {
        // ...
    }

}
Run Code Online (Sandbox Code Playgroud)

在页面加载时异步获取模型

@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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
@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);
    }

}
Run Code Online (Sandbox Code Playgroud)

如果您正在使用JSF实用程序库OmniFaces,如果您使用注释来管理bean,则可以更快地完成此操作ScheduledExecutorService.

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

@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)
    }

}
Run Code Online (Sandbox Code Playgroud)

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

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    @EJB
    private SomeTop100Manager someTop100Manager;

    public List<Some> getSomeTop100() {
        return someTop100Manager.list();
    }

}
Run Code Online (Sandbox Code Playgroud)
@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;
    }

}
Run Code Online (Sandbox Code Playgroud)

也可以看看:

  • 完全同意产卵线程是可以的,只要它是非常小心(完美的措辞).注意,我们最终在EJB 3.1的规范级别解决了这个需求.看我的@Asynchronous答案. (2认同)
  • @BalusC可以详细说明一些工具/功能,可以用来了解会话何时被销毁并结束线程(在EJB 3.0设置中).如果需要,我可以创建一个新问题 (2认同)