preface
Thread pool series:
The thread return value of a Java thread pool
In general, starting a Thread improves the concurrency of a program, and there is no method in the Thread class to obtain the results of a Thread’s execution. Next, we will analyze step by step how to get the results of the thread execution. Through this article, you will learn:
FutureTask obtains thread execution results. 3. Thread pool obtains thread execution results
1. Obtain the thread execution result in the original way
public class ThreadRet { private int sum = 0; public static void main(String args[]) { ThreadRet threadRet = new ThreadRet(); threadRet.startTest(); } private void startTest() { Thread t1 = new Thread(new Runnable() { @Override public void run() { int a = 5; int b = 5; int c = a + b; // Assign the result to the member variable sum = c; System.out.println("c:" + c); }}); t1.start(); Try {// Wait for the thread to complete t1.join(); } catch (InterruptedException e) {e.printStackTrace(); } System.out.println("sum:" + sum); }}Copy the code
The print result is as follows:
The main thread has received the result of thread 1. The principle is simple:
- Thread 1 computes the result, and other threads must wait for it to complete before getting a valid result.
- At this point, you can choose two ways to test the results: polling and wait-notification. Of course, wait-notification is more efficient.
- Thread.join waits until the target Thread finishes executing, or blocks the wait.
The principle of Thread. Join is as follows:Java Thread. Sleep/Thread. Join/Thread. The yield/Object. Wait/Condition. Await explanation
2. FutureTask retrieves the thread execution result
FutureTask use
Although the above method can obtain the results of thread execution, it has the following disadvantages:
1. Each time you need to define a different type of member variable to receive the returned result. 2, each time Thread. Join block wait.
Is there a way to encapsulate these capabilities? It’s time for Callable.
Private void startCall() {// Define Callable, Callable<String> Callable = new Callable() {@override public Object Call () throws Exception {String result = "hello world"; Return result return result; }}; // Define FutureTask, holding Callable reference FutureTask<String> FutureTask = new FutureTask(Callable); New Thread(futureTask).start(); String result = futureTask.get(); System.out.println("result:" + result); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }}Copy the code
The final print is as follows:
You can see that you can get the thread’s execution result correctly.
The operation steps are divided into four steps:
1. Define a Callable, where the specific work of the thread is handled and can return any value. Define the FutureTask, hold the Callable reference, and specify the specific type of the generic that determines the thread’s final return type. This essentially forces the callable.call () return value to a concrete type. Finally, Thread is constructed and passed to FutureTask, which implements Runnable. 4. Obtain thread execution results from FutureTask.
FutureTask principle
Let’s start with the definition of the key class:
#Callable. Java public interface Callable<V> {// Return generic V call() throws Exception; }Copy the code
Callable has only one method that returns a generic type.
Then look at FutureTask:
# futureTask.java public void run() {try {// Callable<V> c = Callable; if (c ! = null && state == NEW) { V result; boolean ran; Try {// Execute the Callable call result = c.call(); ran = true; } catch (Throwable ex) { ... } // Record the result if (ran) set(result); } } finally { ... }} protected void set(V V) {if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) { U.puorderedint (this, STATE, NORMAL); FinishCompletion (); // Final state // Notify other threads waiting for the thread to execute the result. }} private void finishCompletion() {// Waiters work as a header that keeps a log of all other threads waiting for the results of this thread for (WaitNode q; (q = waiters) ! = null;) WAITERS (U.compareAndSwapObject(this, WAITERS, Q, NULL)) {// WAITERS (U.compareAndSwapObject(this, WAITERS, Q, NULL)) {// WAITERS (U.compareAndSwapObject) Thread t = q.htread; if (t ! = null) { q.thread = null; / / wake LockSupport. Unpark (t); } // continue to find the next thread WaitNode next = q.ext; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; }}... }Copy the code
The logic is clear:
1. FutureTask implements Runnable, overwriting the run() method, which is executed when the thread executes, and which ends up calling Callable’s Call () method with the return value recorded in the member variable outcome. 2. When run() completes, the result is available and other threads are notified (wake up).
Since there is a wake up process, there must be a wait process, otherwise the wake up logic is meaningless. FutureTask implements the Future interface and overrides methods like get().
#FutureTask.java public V get() throws InterruptedException, ExecutionException { int s = state; (s <= COMPLETING) // wait s = awaitDone(false, 0L); Return report(s); } private int awaitDone(boolean timed, long nanos) throws InterruptedException { WaitNode q = null; boolean queued = false; for (;;) Timed else if (timed) {// timed else if (timed) {... If (state < COMPLETING) // Thread hangs specified time locksupport. parkNanos(this, parkNanos); } else // Wait until a result returns locksupport.park (this); } } private V report(int s) throws ExecutionException { Object x = outcome; Return (V)x; if (s == NORMAL) return (V)x; . }Copy the code
As can be seen from the above:
Futuretask.get () blocks and waits for thread execution results to return. 2, if there is no result, first join yourself in the waiting list, and can be specified to wait for a certain time, if the time is still no result, directly return. 3. Finally, after the result is executed, force the desired type, in this case String.
The whole process is illustrated as follows:
Compared with original way and the similarities and differences in FutureTask way: the differences Original way through Object. Wait/Object. Notify to implement wait for a notice, and FutureTask by Volatile + CAS + LockSupport wait for a notice.
Similar thread execution results are stored in member variables.
3. The thread pool gets the thread execution result
The small Demo:
Private void startPool () {/ / thread pool ExecutorService service = Executors. NewSingleThreadExecutor (); // Define Callable Callable<String> Callable = new Callable() {@override public Object Call () throws Exception {String result = "hello world"; Return result return result; }}; Future<String> Future = service.submit(callable); try { System.out.println(future.get()); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }}Copy the code
Thread pools provide three ways to obtain thread execution results, all of which rely on Callable+FutureTask internally, although they are used in different ways. Future Submit (Callable Task); The argument passed in is Callable, and callable.run () determines the return value.
The second kind of Future
submit(Runnable task); Runnable. Run () returns no value, so future.get () returns null.
Future submit(Runnable task, T result); Runnable () does not return a value, but future.get () will return result.
conclusion
The above analysis analyzes three ways to obtain thread results (actually two ways, the last two can be summed up as one class), although the methods are different, but the same way. In order to obtain the result of thread execution, there are two core methods:
1. Being able to know when a thread ends. 2. Be able to throw the result (e.g. store it in a member variable).
The next part will focus on the use and principles of thread pools.
If the demo code helps, give Github a thumbs up