开发者

JavaEE6: using @Asynchronous to speed up the web application. When?

开发者 https://www.devze.com 2023-02-15 21:55 出处:网络
I really want to abuse @Asynchronous to speed up my web application, therefore I want to understand this a bit more in order to avoid incorrectly using this annotation.

I really want to abuse @Asynchronous to speed up my web application, therefore I want to understand this a bit more in order to avoid incorrectly using this annotation.

So I know business logic inside this annotated method will be handled in a separated thread, so the user wont have to wait. So I have two method that persist data

public void persist(Object object) {
    em.persist(object);
}

@Asynchronous
public void asynPersist(Object object) {
    em.persist(object);
}

So I have couple scenario I want to ask which one of these scenario is not ok to do

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开发者_如何学JAVA 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?

EDIT: hi @arjan, thank you so much for your post, here is another scenario I want to ask your expertise. Please let me know if my case does not make any sense to u.

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;
}

If I understand you correctly, if my method getUserById use @Asynchronous, then the line u.setCount(u.getCount() + 1); will block until the result of u return, is it correct? So in this case, the use of @Asynchronous is useless, correct?

If the method merge (which merge all changes of u back to database) use @Asynchronous, and if in my JSF page, I have something like this

<p:commandButton value="Increment" actionListener="#{myBean.incVote}" update="cnt"/>
<h:outputText id="cnt" value="#{myBean.user.count}" />

So the button will invoke method incVote(), then send and ajaxical request to tell the outputText to update itself. Will this create an race condition (remember we make merge asynchronous)? So when the button tell the outputText to update itself, it invoke the accessor method getUser(), will the line return user; block to wait for the asynchronous userDAO.merge(user), or there might possible a race condition here (that count might not display the correct result) and therefore not recommend to do so?


There are quite a few places where you can take advantage of @Asynchronous. With this annotation, you can write your application as intended by the Java EE specification; don't use explicit multi-threading but let work being done by managed thread pools.

In the first place you can use this for "fire and forget" actions. E.g. sending an email to a user could be done in an @Asynchronous annotated method. The user does not need to wait for your code to contact the mail-server, negotiate the protocol, etc. It's a waste of everyone's time to let the main request processing thread wait for this.

Likewise, maybe you do some audit logging when a user logs in to your application and logs off again. Both these two persist actions are perfect candidates to put in asynchronous methods. It's senseless to let the user wait for such backend administration.

Then there is a class of situations where you need to fetch multiple data items that can't be (easily) fetched using a single query. For instance, I often see apps that do:

User user = userDAO.getByID(userID);
Invoice invoice = invoiceDAO.getByUserID(userID);
PaymentHistory paymentHistory = paymentDAO.getHistoryByuserID(userID);
List<Order> orders = orderDAO.getOpenOrdersByUserID(userID);

If you execute this as-is, your code will first go the DB and wait for the user to be fetched. It sits idle in between. Then it will go fetch the invoice and waits again. Etc etc.

This can be sped up by doing these individual calls asynchronously:

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);

As soon as you actually need one of those objects, the code will automatically block if the result isn't there yet. This allows you to overlap fetching of individual items and even overlap other processing with fetching. For example, your JSF life cycle might already go through a few phases until you really need any of those objects.

The usual advice that multi threaded programming is hard to debug doesn't really apply here. You're not doing any explicit communication between threads and you're not even creating any threads yourself (which are the two main issues this historical advice is based upon).

For the following case, using asynchronous execution would be useless:

Future<user> futureUser = userDAO.getUserById(userId);
User user = futureUser.get(); // block happens here
user.setCount(user.getCount() + 1);

If you do something asynchronously and right thereafter wait for the result, the net effect is a sequential call.

will the line return user; block to wait for the asynchronous userDAO.merge(user)

I'm afraid you're not totally getting it yet. The return statement has no knowledge about any operation going on for the instance being processed in another context. This is not how Java works.

In my previous example, the getUserByID method returned a Future. The code automatically blocks on the get() operation.

So if you have something like:

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
   }

}

Then in case of the button triggering an AJAX request and the outputText rendering itself with a binding to someBean.user, then there is no race condition. If the dao already did its thing, futureUser will immediately return an instance of type User. Otherwise it will automatically block until the User instance is available.

Regarding doing the merge() operation asynchronous in your example; this might run into race conditions. If your bean is in view scope and the user quickly presses the button again (e.g. perhaps having double clicked the first time) before the original merge is done, an increment might happen on the same instance that the first merge invocation is still persisting.

In this case you have to clone the User object first before sending it to the asynchronous merge operation.

The simple examples I started this answer with are pretty save, as they are about doing an isolated action or about doing reads with an immutable type (the userID, assume it is an int or a String) as input.

As soon as you start passing mutable data into asynchronous methods you'll have to be absolutely certain that there is no mutation being done to that data afterwards, otherwise stick to the simple rule to only pass in immutable data.


You should not use asynch this way if any process that follows the asynch piece depends on the outcome. If you persist data that a later thread needs, you'll have a race condition that will be a bad idea.

I think you should take a step back before you go this route. Write your app as recommended by Java EE: single threaded, with threads handled by the container. Profile your app and find out where the time is being spent. Make a change, reprofile, and see if it had the desired effect.

Multi-threaded apps are hard to write and debug. Don't do this unless you have a good reason and solid data to support your changes.

0

精彩评论

暂无评论...
验证码 换一张
取 消