brief introduction
This article is the opening chapter of Java multithreading. It mainly briefly talks about the status of the next thread and several creation methods, and then analyzes the next FutureTask from the perspective of source code. FutureTask is related to Callable and Runnable, and implements the Future interface, which can well manage our Callable or Runnable.
thread
Let's briefly talk about threads: threads are the smallest unit that the operating system can schedule. They are contained and executed in a process. A process can contain multiple threads, and each thread executes different tasks.
Several states of threads
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
- Initial state new: the initial state created by a thread is the state when a new thread has not start ed
- Running status runnable: when a thread calls the Start method, the thread status becomes ready. At this time, it needs to wait for the CPU to schedule and allocate executable time slices. If it is allocated, it becomes running. Runnable is a general term for ready and running. Both are called running...
- Blocked: this state is entered into the Synchronized method or code block. At this time, the thread needs to wait to obtain the monitor lock object. By the way, the monitor object. Each of our objects will have a corresponding monitor and its association in the JVM. We all know that Synchronized is implemented through monitorEnter and monitorexit, When the JVM executes monitorEnter, it will get the monitor object associated with the current object, and the current monitor will become locked. When subsequent threads execute here, they need to wait to get the monitor object, and the thread state will become blocked
- Waiting: when a thread encounters such methods as object\wait, locksupport\park or thread\join, the thread enters the waiting state and waits for the wake-up signal!
- Limited wait statetime waiting: this is a limited wait. Among the methods I listed above, there is a wait method with wait time. When this method is executed, the thread state is time waiting
- Termination status the status of a Terminated thread after its execution is completed
Several kinds of thread creation
On this issue, some people say there are three creation methods, while others say there are two.
After reading the official website of Oracle, it is actually said that there are two creation methods: https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Thread.html
Directly inherit the Thread interface
The first method is to directly inherit the Thread interface
class MyThread extends Thread { public MyThread() { this.setName("MyThread"); } @Override public void run() { System.out.printf("[%s] is runing", Thread.currentThread().getName()); super.run(); } public static void main(String[] args) { MyThread myThread=new MyThread(); myThread.start(); } }
Runnable
<!--Runnable.java--> public interface Runnable { public abstract void run(); } public static void main(String[] args) { Runnable runnable=new Runnable() { @Override public void run() { System.out.printf("[%s] is runing", Thread.currentThread().getName()); } }; Thread runnableThread=new Thread(runnable); runnableThread.start(); }
Callable
In fact, the essence of this method is the same as that above. Why do you say so?
Callable is used in conjunction with the Future interface. Normally, we use it in this way
<!--Callable.java--> public interface Callable<V> { V call() throws Exception; } Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { return Thread.currentThread().getName() + "---burgxun"; } }; FutureTask<String> futureTask = new FutureTask(callable); Thread callableThread = new Thread(futureTask); callableThread.start(); String msg = futureTask.get(); System.out.println(msg);
FutureTask
FutureTask look at the inheritance structure of this class
public class FutureTask<V> implements RunnableFuture<V> {} public interface RunnableFuture<V> extends Runnable, Future<V>{}
FutureTask essentially implements the Runnable and Future interfaces, so we can pass in FutureTask objects during new Thread(). We know that the Future interface is used to obtain the results of Callable execution, so we can use the FutureTask\get method to obtain the results of Callable execution
FutureTask object is a class that manages tasks. There are 2 tasks, one is a Callable with return value, and the other is a Runable without return value. Take a look at its constructor
/** This is the Callable to be executed by FutureTask */ private Callable<V> callable; /** This is where the running results are stored. The values obtained with get are obtained from the outcome*/ private Object outcome; // non-volatile, protected by state reads/writes /** Thread running FutureTask */ private volatile Thread runner; public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
First of all, we can see that FutureTask has two constructors, one is callable and the other is runnable. We can see that a callable variable in FutureTask is used to store the tasks to be executed. Another thing to pay attention to here is Executors Callable (runnable, result) uses the Executors class, a tool class for task execution. Let's look at the following code:
<!--Executors.java--> public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result); } <!--RunnableAdapter yes Executors Inner class of--> static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }
In fact, the implementation of the RunnableAdapter is also very simple. The RunnableAdapter is a Runnable adapter, which is used to convert runnables into Callable. The input parameter result of the constructor is not normally used. Just pass null.
Future
public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
Looking at the methods defined by the Future interface, we can see that the get method also has a get method to block the time, two methods to judge the task status, and a method to cancel the task. FutureTask inherits RunnableFuture, RunnableFuture implements Future, and FutureTask indirectly implements the Future interface.
Get method
Let's take a look at how the get method for obtaining the Callable run result value is implemented in FutureTask
private volatile int state; private static final int NEW = 0;// private static final int COMPLETING = 1;//In progress status private static final int NORMAL = 2;//Task completion status private static final int EXCEPTIONAL = 3;//Task execution exception private static final int CANCELLED = 4;//Task cancellation private static final int INTERRUPTING = 5;//Task interrupted private static final int INTERRUPTED = 6;//Task interrupted /** * @throws CancellationException {@inheritDoc} */ public V get() throws InterruptedException, ExecutionException { int s = state;//state is the status of the task if (s <= COMPLETING)// If it is less than or equal to COMPLETING, the status value of state is only new,COMPLETING s = awaitDone(false, 0L);//Adding to waiting is infinite waiting return report(s); } private V report(int s) throws ExecutionException { Object x = outcome; if (s == NORMAL)//The NORMAL status indicates that the execution of the task is completed and the value is obtained from the outcome return (V)x; if (s >= CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x); } <!--The thread that gets the value is added to the waiting list--> private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline = timed ? System.nanoTime() + nanos : 0L;//The deadline for waiting. timed is false and the value is 0 WaitNode q = null;//The waiting node is an inner class boolean queued = false; for (;;) {//Spin off waitNode node <!--It is used to detect the interrupt status of the thread during the waiting process in response to the interrupt status--> if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state;//Status of current task if (s > COMPLETING) {// If the task status is greater than 1, it is the following status. The task execution is completed and the loop is exited if (q != null) q.thread = null; return s; } else if (s == COMPLETING) // If the task is still executing, call yield to give up the currently allocated CPU time slice and compete again Thread.yield(); else if (q == null)//The state of s here should be 0, that is, the new state. The WaitNode is created here q = new WaitNode(); else if (!queued) <!--utilize CAS The current waitNode Node put to waiters Will be executed for the first time in the post node of--> queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos); } else LockSupport.park(this);//If the deadline is not set, the waiting thread will be blocked, and the thread status will change to waiting, waiting for the wake-up after the completion of task execution } }
After reading the above process, we know how we use the get method to obtain values. In fact, we obtain values from the outcome field in FutureTask. Although the get method is called get, it will block the thread when the task is not completed and the return value cannot be obtained!
run method
Through the Get method, we know that the result is to obtain the value from the outcome. How do we Get the value of the outcome? Let's see how the run method does it
public void run() { <!--Judge whether the status of the current task is yes or no new,Indicates that the task has been started and returned directly; If CAS Failed to set the execution thread of the current task, which also returns~--> if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) {//Here, you can judge that the task status must be the initial status New V result; boolean ran;//Whether the task is completed try { result = c.call();//Main logic of task execution ran = true;//Execution complete } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result);//set the Result value of to if the task is completed } } } protected void set(V v) { //Modify the execution status of the task in the current FutureTask. It is strange that the modified status is in progress rather than NORMAL. The completion status should not be assigned here. Therefore, the task execution is not completed if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = v; UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // Just after the above, this change comes. This change modifies the status of the task. NORMAL is the final status of the task finishCompletion();//The action to be executed after the task is completed should be to wake up the blocked thread to obtain the value } } /** * Removes and signals all waiting threads, invokes done(), and * nulls out callable. * The English notes are very clear. Remove and wake up all waiting threads, execute the done() method, and set the FutureTask's label to null */ private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) { if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; LockSupport.unpark(t);// Wake up waiting nodes } <!--This is a normal linked list. The operation of removing nodes is a one-way linked list--> WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc set null to disconnect the current node from the linked list to help GC recycle the current node. It should be a reachability analysis algorithm. After disconnection, the current node is unreachable, which is the object to be recycled q = next;// } break; } } done();// Is an empty method that can be inherited and implemented by subclasses callable = null; // The current task is directly completed, and the task body is set to null }
Through the above Run method, the annotation should be clear about how to assign an outCome, how to wake up the waiting blocking thread, and finally get the value through get
summary
I believe this article can help you understand what FutureTask does, how it is associated with Runnable and Callable, and how Callable obtains execution and results!