在 Java EE 中手动启动新线程是否安全?



我找不到一个明确的答案,即在会话范围的 JSF 托管 bean 中生成线程是否安全。线程需要在无状态 EJB 实例(已注入到受管 Bean 的依赖关系(上调用方法。

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

查看 EJB 3.1 @Asynchronous methods 。 这正是他们的用途。

使用 OpenEJB 4.0.0-SNAPSHOTs 的小示例。 在这里,我们有一个@Singleton豆,其中一种方法标记为 @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);
        }
    }
}

下面是一个连续多次调用该方法的小测试用例@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的实例。 相反,它是一个覆盖了所有方法的子类或代理。 应该是异步的方法以不同的方式处理。
  • 对异步方法的调用只会导致创建一个包装您提供的方法和参数的Runnable。 此可运行对象提供给执行器,该执行器只是附加到线程池的工作队列。
  • 将工作添加到队列后,该方法的代理版本返回链接到现在正在队列上等待的RunnableFuture实现。
  • Runnable最终在实际JobProcessor实例上执行该方法时,它将获取返回值并将其设置到Future中,使其可供调用方使用。

请务必注意,JobProcessor返回的AsyncResult对象与调用方持有Future对象不同。 如果真正的JobProcessor可以只返回String并且调用者的JobProcessor版本可以返回Future<String>,那就太好了,但我们没有看到任何方法可以在不增加复杂性的情况下做到这一点。 因此,AsyncResult是一个简单的包装对象。 容器将拉出String,扔掉AsyncResult,然后将String放入调用方持有的真实Future中。

要在此过程中取得进展,只需将线程安全对象(如 AtomicInteger(传递给 @Asynchronous 方法,并让 Bean 代码定期使用完成百分比对其进行更新。

简介

从会话范围的受管 Bean 中生成线程不一定是黑客攻击,只要它完成了您想要的工作。但是,自行生成线程需要格外小心。代码不应该以这样编写的方式编写,例如单个用户可以为每个会话生成无限数量的线程和/或线程即使在会话被销毁后仍继续运行。它迟早会炸毁你的应用程序。

代码需要以这种方式编写,例如,您可以确保用户永远不会在每个会话中生成多个后台线程,并且保证在会话被销毁时线程被中断。对于会话中的多个任务,您需要对任务进行排队。此外,所有这些线程最好由公共线程池提供服务,以便您可以在应用程序级别限制生成的线程总数。

因此,管理线程是一项非常微妙的任务。这就是为什么你最好使用内置设施,而不是和new Thread()和朋友一起在家种植自己的设施。一般的Java EE应用程序服务器提供了一个容器管理的线程池,您可以通过EJB的@Asynchronous@Schedule等方式使用它。要独立于容器(阅读:Tomcat友好(,您还可以使用Java 1.5的Util Concurrent ExecutorServiceScheduledExecutorService

下面的例子假设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)
    }
}
@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);
    }
}

如果您使用的是 JSF 实用程序库 OmniFaces,如果您使用 @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 中为定时任务生成线程

我试过这个,并且在我的 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();
}

相关内容

  • 没有找到相关文章

最新更新