One day, I was stuck with future.get () when I was improving my multithreaded code.
public void Serve () throws Interruptedexception, Executionexception, timeoutexception { final future<response > responsefuture = Asynccode (); Final Response Response = Responsefuture.get (1, SECONDS); Send (response);} private void Send (Response Response) { //...}
This is a Akka application written in Java, using a thread pool with 1000 threads. )-All threads are blocking in this get (). The processing speed of the system cannot keep up with the number of concurrent requests. After the refactoring, we took out all of these threads only retained one, greatly reducing the memory footprint. Let's make this simple by illustrating a Java 8 example. The first step is to use completablefuture to replace the simple future (see: Tip 9).
- By controlling how tasks are submitted to Executorservice: simply use Completablefuture.supplyasync (..., executorservice) instead of Executorservice.submit (...).
- Handling APIs based on callback functions: Using promises
Otherwise (if you have already used a blocking API or future<t>), many threads will be blocked. That's why so many asynchronous APIs are annoying now. So, let's rewrite the previous code to receive completablefuture:
public void Serve () throws Interruptedexception, Executionexception, timeoutexception { final completablefuture <Response> responsefuture = Asynccode (); Final Response Response = Responsefuture.get (1, SECONDS); Send (response);}
Obviously, this does not solve any problems, we must also use the new style to program:
public void Serve () { final completablefuture<response> responsefuture = Asynccode (); Responsefuture.thenaccept (this::send);}
This function is equivalent, but serve () will only run for a short period of time (no blocking or waiting). Just remember: This::send will execute within the same thread that completed responsefuture. If you don't want to overload the existing thread pool or the Send () method too much, consider passing Thenacceptasync (This::send, Sendpool), but we've lost two important attributes: Exception propagation and timeout. Exception propagation is difficult to achieve because we have changed the API. When serve () is present, the asynchronous operation may not yet be completed. If you are concerned about exceptions, consider returning Responsefutureor or other optional mechanisms. At the very least, there should be an unusual log, otherwise the exception will be swallowed up.
Final completablefuture<response> responsefuture = Asynccode (); responsefuture.exceptionally (Throwable- log.error ("Unrecoverable error", throwable); return null;});
Please be careful with the code above: exceptionally () tries to recover from the failure and returns an optional result. This place works fine, but if you use chained calls to exceptionally () and withthenaccept (), even if you fail, the Send () method is called, returning a null parameter, or any other from exceptionally () The value returned in the method.
Responsefuture . Exceptionally (Throwable, { log.error ("Unrecoverable error", throwable); return null; }) . Thenaccept (this::send); Probably not think
The problem of missing a second timeout is very ingenious. Our original code waits (blocks) for up to 1 seconds before the future is complete, otherwise it throws timeoutexception. We lost this feature, and worse, the unit test timed out not very convenient, often skipping this link. To maintain the timeout mechanism without destroying the event-driven principle, we need to create an additional module: a future that must fail at a given time.
public static <T> completablefuture<t> failafter (Duration Duration) { final completablefuture<t > Promise = new completablefuture<> (); Scheduler.schedule (() { final timeoutexception ex = new TimeoutException ("Timeout after" + duration); Return promise.completeexceptionally (ex); }, Duration.tomillis (), MILLISECONDS); return promise;} Private static final Scheduledexecutorservice scheduler = Executors.newscheduledthreadpool ( 1, new Threadfactorybuilder () . Setdaemon (True). Setnameformat ("failafter-%d") . Build ());
This is simple: we create a promise (without a background task or a thread pool's future), and then throw a TimeoutException exception after a given java.time.Duration. If you call get () in a certain place to get the future, the blocked time will be thrown timeoutexception when it reaches this specified time.
In fact, it's a executionexception that wraps up timeoutexception, and this doesn't need to be said much. Note that I used a thread pool that fixed a thread. This is not just for the purpose of teaching: This is the scenario where 1 threads should be able to meet anyone's needs. Failafter () is not very useful in itself, but if used with ourresponsefuture, we can solve the problem.
Final completablefuture<response> responsefuture = Asynccode (); final completablefuture<response> Onesecondtimeout = Failafter (duration.ofseconds (1)); Responsefuture . Accepteither (Onesecondtimeout, This::send ) . Exceptionally (Throwable, { log.error ("Problem", throwable); return null; });
There are a lot of other things to do here. When a task in the background receives responsefuture, we also create a "synthetic" onesecondtimeout future, which is never executed at the time of success, but causes the task to fail after 1 seconds. Now that we are combining these two called accepteither, this operation will execute the code block that completes the future first, and simply ignore the slower one in Responsefuture or onesecondtimeout. If the Asynccode () code executes within 1 seconds, the this::send is called, and the Onesecondtimeout exception is not thrown. However, if Asynccode () execution is really slow, the Onesecondtimeout exception is thrown first. Because an exception causes the task to fail, the Exceptionallyerror processor is called instead of the This::send method. You can choose to execute Send () or exceptionally, but not both. When, for example, if we have two "normal" future normal execution completed, then the first response will call the Send () method, the following will be discarded.
This is not the most clear solution. A clearer solution is to wrap the original future and then make sure it executes within a given time. This operation is feasible for Com.twitter.util.Future (Scala is called Within ()), but there is no such function in scala.concurrent.Future (presumably to encourage the use of the previous method). We're not going to talk about how Scala is performing at the moment, like Completablefuture. It takes a future as input, and then returns a future, which executes when the background task finishes. However, if the underlying future execution takes too long, it throws an exception:
public static <T> completablefuture<t> within (completablefuture<t>, Duration Duration) { Final completablefuture<t> timeout = failafter (duration); Return Future.applytoeither (timeout, function.identity ());}
This leads us to a final, clear, and flexible approach:
Final completablefuture<response> responsefuture = Within ( asynccode (), Duration.ofseconds (1)); Responsefuture . Thenaccept (This::send) . Exceptionally (Throwable, Log.error ("unrecoverable Error ", throwable); return null; });
I hope you enjoy this article because you already know that in Java, implementing responsive programming is no longer a problem.
Crazy Java Learning-----------processing asynchronous timeouts using Completablefuture