Hello, I'm Xiao Hei, a migrant worker who lives on the Internet.
Runnable
When creating a Thread, you can use the new Thread(Runnable) method to encapsulate the task code in the run() method of Runnable, submit the Runnable to the Thread as a task, or use the execute(Runnable) method of the Thread pool for processing.
public class RunnableDemo { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(new MyRunnable()); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println("runnable Executing"); } }
Runnable problem
If you have seen or written Runnable related code before, you will certainly see that Runnable cannot obtain task execution results, which is the problem of Runnable. Can you transform it to meet the use of Runnable and obtain task execution results? The answer is yes, but it will be more troublesome.
First, we cannot modify the run() method to have a return value, which violates the principle of interface implementation; We can do this in three steps:
- We can define variables in the user-defined Runnable to store the calculation results;
- Provide external methods so that external can obtain results through methods;
- Before the end of task execution, if the external wants to obtain the result, block it;
If you have read my previous articles, I believe that the function is not complex. For the specific implementation, you can see my following code.
public class RunnableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { MyRunnable<String> myRunnable = new MyRunnable<>(); new Thread(myRunnable).start(); System.out.println(LocalDateTime.now() + " myRunnable start-up~"); MyRunnable.Result<String> result = myRunnable.getResult(); System.out.println(LocalDateTime.now() + " " + result.getValue()); } } class MyRunnable<T> implements Runnable { // Use result as the storage variable of the return value, and use volatile decoration to prevent instruction rearrangement private volatile Result<T> result; @Override public void run() { // In this process, the result will be assigned to ensure that the external thread cannot obtain it during assignment, so the lock is added synchronized (this) { try { TimeUnit.SECONDS.sleep(2); System.out.println(LocalDateTime.now() + " run Method is executing"); result = new Result("This is the result returned"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // Wake up the waiting thread after assignment this.notifyAll(); } } } // Method is locked, and only one thread can obtain it public synchronized Result<T> getResult() throws InterruptedException { // Loop check whether the result has been assigned while (result == null) { // If there is no assignment, wait this.wait(); } return result; } // Wrap the result with an inner class instead of directly using T as the return result // It can support the case that the return value is equal to null static class Result<T> { T value; public Result(T value) { this.value = value; } public T getValue() { return value; } } }
From the running results, we can see that the return results of Runnable can be obtained in the main thread.
The above code seems to meet our requirements functionally, but there are many problems of concurrency, which is not recommended in actual development. In our actual work scenario, there are many such situations. We can't customize one set every time, and it's easy to make mistakes, resulting in thread safety problems. In JDK, we have provided us with a special API to meet our requirements, which is Callable.
Callable
We use Callable to complete the accumulation function of 100-100 million mentioned above.
public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { Long max = 100_000_000L; Long avgCount = max % 3 == 0 ? max / 3 : max / 3 + 1; // Store results in FutureTask List<FutureTask<Long>> tasks = new ArrayList<>(); for (int i = 0; i < 3; i++) { Long begin = 1 + avgCount * i; Long end = 1 + avgCount * (i + 1); if (end > max) { end = max; } FutureTask<Long> task = new FutureTask<>(new MyCallable(begin, end)); tasks.add(task); new Thread(task).start(); } for (FutureTask<Long> task : tasks) { // Get task processing results from task System.out.println(task.get()); } } } class MyCallable implements Callable<Long> { private final Long min; private final Long max; public MyCallable(Long min, Long max) { this.min = min; this.max = max; } @Override public Long call() { System.out.println("min:" + min + ",max:" + max); Long sum = 0L; for (Long i = min; i < max; i++) { sum = sum + i; } // Calculation results can be returned return sum; } }
Operation results:
When creating a Thread, you can encapsulate the Callable object in the FutureTask object and hand it over to the Thread object for execution.
FutureTask can be created as a parameter of Thread because FutureTask is an implementation class of Runnable interface.
Since FutureTask is also an implementation class of the Runnable interface, there must also be a run() method. Let's see how to have a return value through the source code.
First, there is the following information in FutureTask.
public class FutureTask<V> implements RunnableFuture<V> { // Status of the task private volatile int state; private static final int NEW = 0; private static final int COMPLETING = 1; private static final int NORMAL = 2; private static final int EXCEPTIONAL = 3; private static final int CANCELLED = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED = 6; // Specific task object private Callable<V> callable; // The exception object returned when the task returns the result or exception private Object outcome; // Currently running thread private volatile Thread runner; // private volatile WaitNode waiters; private static final sun.misc.Unsafe UNSAFE; private static final long stateOffset; private static final long runnerOffset; private static final long waitersOffset; }
public void run() { // Verification of task status if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { // Execute the call method of callable to obtain the result result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; // If there is an exception, set the return value to ex setException(ex); } // If there is no exception in the execution process, set the result if (ran) set(result); } } finally { runner = null; int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
The core logic in this method is to execute the call() method of callable, assign the result, and encapsulate the exception if there is an exception.
Then let's look at how the get method gets the result.
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) // There will be a jam waiting here s = awaitDone(false, 0L); // Return results return report(s); } private V report(int s) throws ExecutionException { Object x = outcome; if (s == NORMAL) return (V)x; if (s >= CANCELLED) // An exception will be thrown if the status is abnormal throw new CancellationException(); throw new ExecutionException((Throwable)x); }
In FutureTask, there are some other methods besides get() method.
-
get(timeout,unit): get the result, but only wait for the specified time;
-
cancel(boolean mayInterruptIfRunning): cancels the current task;
-
isDone(): judge whether the task has been completed.
CompletableFuture
When using FutureTask to complete asynchronous tasks and obtain results through the get() method, the thread that obtains the results will enter blocking waiting. This method is not the most ideal state.
In JDK8, completabilefuture is introduced to improve the Future. When defining the completabilefuture, the callback object can be passed in. When the task is completed or abnormal, it will be called back automatically.
public class CompletableFutureDemo { public static void main(String[] args) throws InterruptedException { // Supplier object passed in when creating completabilefuture CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new MySupplier()); //When the execution is successful future.thenAccept(new MyConsumer()); // Execution exception future.exceptionally(new MyFunction()); // The main task can continue to be processed without waiting for the task to be executed System.out.println("The main thread continues execution"); Thread.sleep(5000); System.out.println("End of main thread execution"); } } class MySupplier implements Supplier<Integer> { @Override public Integer get() { try { // Task sleep 3s TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return 3 + 2; } } // Callback the Consumer object when the task is completed class MyConsumer implements Consumer<Integer> { @Override public void accept(Integer integer) { System.out.println("results of enforcement" + integer); } } // Callback Function object when task execution is abnormal class MyFunction implements Function<Throwable, Integer> { @Override public Integer apply(Throwable type) { System.out.println("Execution exception" + type); return 0; } }
The above code can be simplified by lambda expressions.
public class CompletableFutureDemo { public static void main(String[] args) throws InterruptedException { CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { try { // Task sleep 3s TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return 3 + 2; }); //When the execution is successful future.thenAccept((x) -> { System.out.println("results of enforcement" + x); }); future.exceptionally((type) -> { System.out.println("Execution exception" + type); return 0; }); System.out.println("The main thread continues execution"); Thread.sleep(5000); System.out.println("End of main thread execution"); } }
Through the example, we find the advantages of completable future:
- When the asynchronous task ends, it will automatically call back the method of an object;
- When an asynchronous task makes an error, it will automatically call back the method of an object;
- After setting the callback, the main thread no longer cares about the execution of asynchronous tasks.
Of course, these advantages are not enough to reflect the powerful and more powerful functions of completable future.
Serial execution
Multiple completable future can be executed serially. For example, the first task is queried first, and the second task is updated
public class CompletableFutureDemo { public static void main(String[] args) throws InterruptedException { // First task CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1234); // Second task CompletableFuture<Integer> secondFuture = future.thenApplyAsync((num) -> { System.out.println("num:" + num); return num + 100; }); secondFuture.thenAccept(System.out::println); System.out.println("The main thread continues execution"); Thread.sleep(5000); System.out.println("End of main thread execution"); } }
Parallel tasks
In addition to serialization, completable future also supports parallel processing.
public class CompletableFutureDemo { public static void main(String[] args) throws InterruptedException { // First task CompletableFuture<Integer> oneFuture = CompletableFuture.supplyAsync(() -> 1234); // Second task CompletableFuture<Integer> twoFuture = CompletableFuture.supplyAsync(() -> 5678); // Merge two tasks into one parallel task through anyOf CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(oneFuture, twoFuture); anyFuture.thenAccept(System.out::println); System.out.println("The main thread continues execution"); Thread.sleep(5000); System.out.println("End of main thread execution"); } }
Multiple tasks can be realized through anyOf(). Only one task is successful. Completabilefuture also has an allOf() method to realize the merged task after multiple tasks must be successful.
Summary
The asynchronous thread implemented by the Runnable interface cannot return the result of task operation by default. Of course, it can be returned through modification, but it is too complex to be modified;
The Callable interface and FutureTask can meet the return of asynchronous task results, but there is a problem. The main thread will block waiting when it cannot obtain the results;
Completabilefuture is enhanced. It only needs to specify the callback object at the end of task execution or exception, which will be executed automatically after completion, and supports advanced methods such as serial, parallel and execution after multiple tasks are executed.
The above is the whole content of this issue. I'll see you next time. If you think it's useful, pay attention.