我真的想滥用@Asynchronous
来加速我的网络应用程序,因此我想更多地理解这一点,以避免错误地使用此注释。
所以我知道这个带注释的方法中的业务逻辑将在一个单独的线程中处理,因此用户不必等待。所以我有两种持久化数据的方法
public void persist(Object object) {
em.persist(object);
}
@Asynchronous
public void asynPersist(Object object) {
em.persist(object);
}
所以我有几个场景,我想问一下这些场景中哪一个不行
1. B is not depend on A
A a = new A();
asynPersist(a); //Is it risky to `asynPersist(a) here?`
B b = new B();
persist(b);
//Cant asynPersist(B) here because I need the `b` to immediately
//reflect to the view, or should I `asynPersist(b)` as well?
2. Same as first scenario but B now depend on A. Should I `asynPersist(a)`
3. A and B are not related
A a = new A();
persist(a); //Since I need `a` content to reflect on the view
B b = new B();
asynPersist(b); //If I dont need content of b to immediately reflect on the view. Can I use asyn here?
编辑:嗨@arjan,非常感谢您的帖子,这是另一种情况,我想问你的专业知识。如果我的案件对你没有任何意义,请告诉我。
4. Assume User has an attribute call `count` type `int`
User user = null;
public void incVote(){
user = userDAO.getUserById(userId);
user.setCount(u.getCount() + 1);
userDAO.merge(user);
}
public User getUser(){ //Accessor method of user
return user;
}
如果我理解正确,如果我的方法getUserById
使用@Asynchronous
,则行u.setCount(u.getCount() + 1);
将会阻止,直到u
的结果返回,是否正确?所以在这种情况下,使用@Asynchronous
是没用的,对吗?
如果方法merge
(将u
的所有更改合并回数据库)使用@Asynchronous
,并且如果在我的JSF页面中,我有类似的内容
<p:commandButton value="Increment" actionListener="#{myBean.incVote}" update="cnt"/>
<h:outputText id="cnt" value="#{myBean.user.count}" />
因此按钮将调用方法incVote()
,然后发送和ajaxical请求告诉outputText
更新自己。这会创建竞争条件(还记得我们使合并异步)吗?因此,当按钮告诉outputText
更新自身时,它会调用访问器方法getUser()
,行return user;
阻止等待异步userDAO.merge(user)
,或者有可能这里的竞争条件(该计数可能无法显示正确的结果),因此不建议这样做?
答案 0 :(得分:10)
有很多地方可以利用@Asynchronous。使用此批注,您可以按照Java EE规范的预期编写应用程序;不要使用显式多线程,而是让托管线程池完成工作。
首先,您可以将其用于“即发即弃”行动。例如。向用户发送电子邮件可以在@Asynchronous注释方法中完成。用户无需等待代码联系邮件服务器,协商协议等。让主要请求处理线程等待这一点是浪费每个人的时间。
同样,当用户登录您的应用程序并再次注销时,您可能会执行一些审核日志记录。这两个持久化动作都是放入异步方法的完美候选者。让用户等待这样的后端管理毫无意义。
然后,有一类情况需要获取使用单个查询无法(轻松)获取的多个数据项。例如,我经常看到以下应用:
User user = userDAO.getByID(userID);
Invoice invoice = invoiceDAO.getByUserID(userID);
PaymentHistory paymentHistory = paymentDAO.getHistoryByuserID(userID);
List<Order> orders = orderDAO.getOpenOrdersByUserID(userID);
如果按原样执行此操作,您的代码将首先进入数据库并等待提取用户。它闲置在中间。然后它将获取发票并再次等待。等等。
这可以通过异步执行这些单独调用来加速:
Future<User> futureUser = userDAO.getByID(userID);
Future<Invoice> futureInvoice = invoiceDAO.getByUserID(userID);
Future<PaymentHistory> futurePaymentHistory = paymentDAO.getHistoryByuserID(userID);
Future<List<Order>> futureOrders = orderDAO.getOpenOrdersByUserID(userID);
只要您确实需要其中一个对象,如果结果尚未存在,代码将自动阻止。这允许您重叠提取单个项目,甚至与提取重叠其他处理。例如,您的JSF生命周期可能已经经历了几个阶段,直到您确实需要任何这些对象。
多线程编程难以调试的常见建议并不适用于此。你没有在线程之间进行任何明确的沟通,你甚至没有自己创建任何线程(这是这个历史建议所基于的两个主要问题)。
对于以下情况,使用异步执行将毫无用处:
Future<user> futureUser = userDAO.getUserById(userId);
User user = futureUser.get(); // block happens here
user.setCount(user.getCount() + 1);
如果您异步执行某些操作并在此后等待结果,则净效应是顺序调用。
该行将返回用户;阻止等待异步userDAO.merge(用户)
我担心你还没完全得到它。 return语句不知道在另一个上下文中正在处理的实例正在进行的任何操作。这不是Java的工作方式。
在我之前的示例中,getUserByID
方法返回了Future。代码会自动阻止get()
操作。
所以如果你有类似的东西:
public class SomeBean {
Future<User> futureUser;
public String doStuff() {
futureUser = dao.getByID(someID);
return "";
}
public getUser() {
return futureUser.get(); // blocks in case result is not there
}
}
然后,如果按钮触发AJAX请求并且outputText呈现自身并绑定到someBean.user
,那么就没有竞争条件。如果dao已经做了它的事情,futureUser将立即返回User类型的实例。否则它将自动阻止,直到User实例可用。
关于在您的示例中执行异步merge()
操作;这可能会遇到竞争条件。如果您的bean在视图范围内并且用户在原始合并完成之前再次快速按下该按钮(例如,可能第一次双击),则在第一次合并调用仍然持久的同一实例上可能会发生增量。 / p>
在这种情况下,您必须首先克隆User对象,然后再将其发送到异步合并操作。
我开始回答的简单示例非常省略,因为它们是关于执行隔离操作或者使用不可变类型(userID,假设它是int或String)作为输入进行读取。
一旦你开始将可变数据传递给异步方法,你就必须绝对确定之后没有对该数据进行任何变异,否则坚持使用简单规则来传递不可变数据。
答案 1 :(得分:1)
如果异步片后面的任何进程取决于结果,则不应该以这种方式使用异步。如果您保留后来线程需要的数据,那么您将遇到一个不好主意的竞争条件。
我认为你应该在走这条路之前退后一步。按照Java EE的建议编写应用程序:单线程,由容器处理线程。描述您的应用并找出花费的时间。进行更改,重新配置,并查看它是否具有所需的效果。
多线程应用程序很难编写和调试。除非您有充分的理由和可靠的数据来支持您的更改,否则不要这样做。