[Alta concurrencia] Dos modelos asincrónicos y análisis en profundidad de la interfaz Future

Hola a todos, soy Glacier~~

Este artículo es un poco largo, pero está lleno de productos secos. Analiza dos modelos asincrónicos en forma de casos reales, y analiza la interfaz Future y la clase FutureTask en profundidad desde la perspectiva del código fuente. Creo que obtendrá una ¡lote!

Uno, dos modelos asíncronos

En la programación concurrente de Java, generalmente hay dos modelos de programación asíncrona, uno es ejecutar otras tareas en paralelo de forma asíncrona sin devolver los datos de resultado de la tarea. Una es ejecutar otras tareas de forma asíncrona y necesitar devolver resultados.

1. Modelo asíncrono sin resultado de retorno

Para las tareas asincrónicas que no devuelven resultados, la tarea se puede enviar directamente al subproceso o grupo de subprocesos para que se ejecute. En este momento, los datos del resultado de la ejecución de la tarea no se pueden obtener directamente. Una forma es utilizar el método de devolución de llamada para obtener el resultado de ejecución de la tarea.

La solución específica es: definir una interfaz de devolución de llamada y definir un método para recibir datos de resultados de tareas en la interfaz, y la lógica específica se completa en la clase de implementación de la interfaz de devolución de llamada. Coloque la interfaz de devolución de llamada junto con los parámetros de la tarea en el subproceso o grupo de subprocesos para ejecutar, llame al método de interfaz después de que se ejecute la tarea y ejecute la lógica en la clase de implementación de la interfaz de devolución de llamada para procesar los datos de resultado. Aquí, se da un ejemplo simple como referencia.

  • Definir la interfaz de devolución de llamada
package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 定义回调接口
 */
public interface TaskCallable<T> {
    T callable(T t);
}
复制代码

Para facilitar el tipo genérico de la interfaz, aquí se define un tipo genérico para la interfaz de devolución de llamada.

  • Define la clase de encapsulación para los datos de resultados de tareas
package io.binghe.concurrent.lab04;

import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 任务执行结果
 */
public class TaskResult implements Serializable {
    private static final long serialVersionUID = 8678277072402730062L;
    /**
     * 任务状态
     */
    private Integer taskStatus;

    /**
     * 任务消息
     */
    private String taskMessage;

    /**
     * 任务结果数据
     */
    private String taskResult;
	
	//省略getter和setter方法
	@Override
    public String toString() {
        return "TaskResult{" +
                "taskStatus=" + taskStatus +
                ", taskMessage='" + taskMessage + '\'' +
                ", taskResult='" + taskResult + '\'' +
                '}';
    }
}
复制代码
  • Crear una clase de implementación de la interfaz de devolución de llamada

La clase de implementación de la interfaz de devolución de llamada se utiliza principalmente para realizar el procesamiento comercial correspondiente en el resultado devuelto de la tarea Aquí, por conveniencia de demostración, solo se devuelven los datos del resultado. Necesitamos hacer el análisis y el procesamiento correspondientes de acuerdo con los escenarios comerciales específicos.

package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 回调函数的实现类
 */
public class TaskHandler implements TaskCallable<TaskResult> {
    @Override
public TaskResult callable(TaskResult taskResult) {
//TODO 拿到结果数据后进一步处理
    System.out.println(taskResult.toString());
        return taskResult;
    }
}
复制代码
  • Crear una clase de ejecución de tareas

La clase de ejecución de la tarea es la clase que ejecuta específicamente la tarea, implementa la interfaz Runnable, define una variable miembro del tipo de interfaz de devolución de llamada y un parámetro de tarea del tipo String (parámetros de la tarea simulada) en esta clase, e inyecta la interfaz de devolución de llamada y los parámetros de la tarea. La tarea se ejecuta en el método de ejecución. Una vez completada la tarea, los datos de resultado de la tarea se encapsulan en un objeto TaskResult y se llama al método de la interfaz de devolución de llamada para pasar el objeto TaskResult al método de devolución de llamada.

package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 任务执行类
 */
public class TaskExecutor implements Runnable{
    private TaskCallable<TaskResult> taskCallable;
    private String taskParameter;

    public TaskExecutor(TaskCallable<TaskResult> taskCallable, String taskParameter){
        this.taskCallable = taskCallable;
        this.taskParameter = taskParameter;
    }

    @Override
    public void run() {
        //TODO 一系列业务逻辑,将结果数据封装成TaskResult对象并返回
        TaskResult result = new TaskResult();
        result.setTaskStatus(1);
        result.setTaskMessage(this.taskParameter);
        result.setTaskResult("异步回调成功");
        taskCallable.callable(result);
    }
}
复制代码

En este punto, se completa todo el gran marco y el siguiente paso es probar si se pueden obtener los resultados de las tareas asincrónicas.

  • Clase de prueba de tarea asíncrona
package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试回调
 */
public class TaskCallableTest {
    public static void main(String[] args){
        TaskCallable<TaskResult> taskCallable = new TaskHandler();
        TaskExecutor taskExecutor = new TaskExecutor(taskCallable, "测试回调任务");
        new Thread(taskExecutor).start();
    }
}
复制代码

En la clase de prueba, use la clase Thread para crear un nuevo hilo e iniciar el hilo para ejecutar la tarea. Los datos finales de la interfaz del programa en ejecución se muestran a continuación.

TaskResult{taskStatus=1, taskMessage='测试回调任务', taskResult='异步回调成功'}
复制代码

大家可以细细品味下这种获取异步结果的方式。这里,只是简单的使用了Thread类来创建并启动线程,也可以使用线程池的方式实现。大家可自行实现以线程池的方式通过回调接口获取异步结果。

2.有返回结果的异步模型

尽管使用回调接口能够获取异步任务的结果,但是这种方式使用起来略显复杂。在JDK中提供了可以直接返回异步结果的处理方案。最常用的就是使用Future接口或者其实现类FutureTask来接收任务的返回结果。

  • 使用Future接口获取异步结果

使用Future接口往往配合线程池来获取异步执行结果,如下所示。

package io.binghe.concurrent.lab04;

import java.util.concurrent.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试Future获取异步结果
 */
public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "测试Future获取异步结果";
            }
        });
        System.out.println(future.get());
        executorService.shutdown();
    }
}
复制代码

运行结果如下所示。

测试Future获取异步结果
复制代码
  • 使用FutureTask类获取异步结果

FutureTask类既可以结合Thread类使用也可以结合线程池使用,接下来,就看下这两种使用方式。

结合Thread类的使用示例如下所示。

package io.binghe.concurrent.lab04;

import java.util.concurrent.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试FutureTask获取异步结果
 */
public class FutureTaskTest {

    public static void main(String[] args)throws ExecutionException, InterruptedException{
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "测试FutureTask获取异步结果";
            }
        });
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}
复制代码

运行结果如下所示。

测试FutureTask获取异步结果
复制代码

结合线程池的使用示例如下。

package io.binghe.concurrent.lab04;

import java.util.concurrent.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试FutureTask获取异步结果
 */
public class FutureTaskTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "测试FutureTask获取异步结果";
            }
        });
        executorService.execute(futureTask);
        System.out.println(futureTask.get());
        executorService.shutdown();
    }
}
复制代码

运行结果如下所示。

测试FutureTask获取异步结果
复制代码

可以看到使用Future接口或者FutureTask类来获取异步结果比使用回调接口获取异步结果简单多了。注意:实现异步的方式很多,这里只是用多线程举例。

接下来,就深入分析下Future接口。

二、深度解析Future接口

1.Future接口

Future是JDK1.5新增的异步编程接口,其源代码如下所示。

package java.util.concurrent;

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;
}
复制代码

可以看到,在Future接口中,总共定义了5个抽象方法。接下来,就分别介绍下这5个方法的含义。

  • cancel(boolean)

取消任务的执行,接收一个boolean类型的参数,成功取消任务,则返回true,否则返回false。当任务已经完成,已经结束或者因其他原因不能取消时,方法会返回false,表示任务取消失败。当任务未启动调用了此方法,并且结果返回true(取消成功),则当前任务不再运行。如果任务已经启动,会根据当前传递的boolean类型的参数来决定是否中断当前运行的线程来取消当前运行的任务。

  • isCancelled()

判断任务在完成之前是否被取消,如果在任务完成之前被取消,则返回true;否则,返回false。

这里需要注意一个细节:只有任务未启动,或者在完成之前被取消,才会返回true,表示任务已经被成功取消。其他情况都会返回false。

  • isDone()

判断任务是否已经完成,如果任务正常结束、抛出异常退出、被取消,都会返回true,表示任务已经完成。

  • get()

当任务完成时,直接返回任务的结果数据;当任务未完成时,等待任务完成并返回任务的结果数据。

  • get(long, TimeUnit)

当任务完成时,直接返回任务的结果数据;当任务未完成时,等待任务完成,并设置了超时等待时间。在超时时间内任务完成,则返回结果;否则,抛出TimeoutException异常。

2.RunnableFuture接口

Future接口有一个重要的子接口,那就是RunnableFuture接口,RunnableFuture接口不但继承了Future接口,而且继承了java.lang.Runnable接口,其源代码如下所示。

package java.util.concurrent;

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
复制代码

这里,问一下,RunnableFuture接口中有几个抽象方法?想好了再说!哈哈哈。。。

这个接口比较简单run()方法就是运行任务时调用的方法。

3.FutureTask类

FutureTask类是RunnableFuture接口的一个非常重要的实现类,它实现了RunnableFuture接口、Future接口和Runnable接口的所有方法。FutureTask类的源代码比较多,这个就不粘贴了,大家自行到java.util.concurrent下查看。

(1)FutureTask类中的变量与常量

在FutureTask类中首先定义了一个状态变量state,这个变量使用了volatile关键字修饰,这里,大家只需要知道volatile关键字通过内存屏障和禁止重排序优化来实现线程安全,后续会单独深度分析volatile关键字是如何保证线程安全的。紧接着,定义了几个任务运行时的状态常量,如下所示。

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;
复制代码

其中,代码注释中给出了几个可能的状态变更流程,如下所示。

NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
复制代码

接下来,定义了其他几个成员变量,如下所示。

private Callable<V> callable;
private Object outcome; 
private volatile Thread runner;
private volatile WaitNode waiters;
复制代码

又看到我们所熟悉的Callable接口了,Callable接口那肯定就是用来调用call()方法执行具体任务了。

  • outcome:Object类型,表示通过get()方法获取到的结果数据或者异常信息。
  • runner:运行Callable的线程,运行期间会使用CAS保证线程安全,这里大家只需要知道CAS是Java保证线程安全的一种方式,后续文章中会深度分析CAS如何保证线程安全。
  • waiters:WaitNode类型的变量,表示等待线程的堆栈,在FutureTask的实现中,会通过CAS结合此堆栈交换任务的运行状态。

看一下WaitNode类的定义,如下所示。

static final class WaitNode {
	volatile Thread thread;
	volatile WaitNode next;
	WaitNode() { thread = Thread.currentThread(); }
}
复制代码

可以看到,WaitNode类是FutureTask类的静态内部类,类中定义了一个Thread成员变量和指向下一个WaitNode节点的引用。其中通过构造方法将thread变量设置为当前线程。

(2)构造方法

接下来,是FutureTask的两个构造方法,比较简单,如下所示。

public FutureTask(Callable<V> callable) {
	if (callable == null)
		throw new NullPointerException();
	this.callable = callable;
	this.state = NEW;
}

public FutureTask(Runnable runnable, V result) {
	this.callable = Executors.callable(runnable, result);
	this.state = NEW;
}
复制代码

(3)是否取消与完成方法

继续向下看源码,看到一个任务是否取消的方法,和一个任务是否完成的方法,如下所示。

public boolean isCancelled() {
	return state >= CANCELLED;
}

public boolean isDone() {
	return state != NEW;
}
复制代码

这两方法中,都是通过判断任务的状态来判定任务是否已取消和已完成的。为啥会这样判断呢?再次查看FutureTask类中定义的状态常量发现,其常量的定义是有规律的,并不是随意定义的。其中,大于或者等于CANCELLED的常量为CANCELLED、INTERRUPTING和INTERRUPTED,这三个状态均可以表示线程已经被取消。当状态不等于NEW时,可以表示任务已经完成。

通过这里,大家可以学到一点:以后在编码过程中,要按照规律来定义自己使用的状态,尤其是涉及到业务中有频繁的状态变更的操作,有规律的状态可使业务处理变得事半功倍,这也是通过看别人的源码设计能够学到的,这里,建议大家还是多看别人写的优秀的开源框架的源码。

(4)取消方法

我们继续向下看源码,接下来,看到的是cancel(boolean)方法,如下所示。

public boolean cancel(boolean mayInterruptIfRunning) {
	if (!(state == NEW &&
		  UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
			  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
		return false;
	try {    // in case call to interrupt throws exception
		if (mayInterruptIfRunning) {
			try {
				Thread t = runner;
				if (t != null)
					t.interrupt();
			} finally { // final state
				UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
			}
		}
	} finally {
		finishCompletion();
	}
	return true;
}
复制代码

接下来,拆解cancel(boolean)方法。在cancel(boolean)方法中,首先判断任务的状态和CAS的操作结果,如果任务的状态不等于NEW或者CAS的操作返回false,则直接返回false,表示任务取消失败。如下所示。

if (!(state == NEW &&
	  UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
		  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
	return false;
复制代码

接下来,在try代码块中,首先判断是否可以中断当前任务所在的线程来取消任务的运行。如果可以中断当前任务所在的线程,则以一个Thread临时变量来指向运行任务的线程,当指向的变量不为空时,调用线程对象的interrupt()方法来中断线程的运行,最后将线程标记为被中断的状态。如下所示。

try {
	if (mayInterruptIfRunning) {
		try {
			Thread t = runner;
			if (t != null)
				t.interrupt();
		} finally { // final state
			UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
		}
	}
}
复制代码

这里,发现变更任务状态使用的是UNSAFE.putOrderedInt()方法,这个方法是个什么鬼呢?点进去看一下,如下所示。

public native void putOrderedInt(Object var1, long var2, int var4);
复制代码

可以看到,又是一个本地方法,嘿嘿,这里先不管它,后续文章会详解这些方法的作用。

接下来,cancel(boolean)方法会进入finally代码块,如下所示。

finally {
	finishCompletion();
}
复制代码

可以看到在finallly代码块中调用了finishCompletion()方法,顾名思义,finishCompletion()方法表示结束任务的运行,接下来看看它是如何实现的。点到finishCompletion()方法中看一下,如下所示。

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);
				}
				WaitNode next = q.next;
				if (next == null)
					break;
				q.next = null; // unlink to help gc
				q = next;
			}
			break;
		}
	}
	done();
	callable = null;        // to reduce footprint
}
复制代码

在finishCompletion()方法中,首先定义一个for循环,循环终止因子为waiters为null,在循环中,判断CAS操作是否成功,如果成功进行if条件中的逻辑。首先,定义一个for自旋循环,在自旋循环体中,唤醒WaitNode堆栈中的线程,使其运行完成。当WaitNode堆栈中的线程运行完成后,通过break退出外层for循环。接下来调用done()方法。done()方法又是个什么鬼呢?点进去看一下,如下所示。

protected void done() { }
复制代码

可以看到,done()方法是一个空的方法体,交由子类来实现具体的业务逻辑。

当我们的具体业务中,需要在取消任务时,执行一些额外的业务逻辑,可以在子类中覆写done()方法的实现。

(5)get()方法

继续向下看FutureTask类的代码,FutureTask类中实现了两个get()方法,如下所示。

public V get() throws InterruptedException, ExecutionException {
	int s = state;
	if (s <= COMPLETING)
		s = awaitDone(false, 0L);
	return report(s);
}

public V get(long timeout, TimeUnit unit)
	throws InterruptedException, ExecutionException, TimeoutException {
	if (unit == null)
		throw new NullPointerException();
	int s = state;
	if (s <= COMPLETING &&
		(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
		throw new TimeoutException();
	return report(s);
}
复制代码

没参数的get()方法为当任务未运行完成时,会阻塞,直到返回任务结果。有参数的get()方法为当任务未运行完成,并且等待时间超出了超时时间,会TimeoutException异常。

两个get()方法的主要逻辑差不多,一个没有超时设置,一个有超时设置,这里说一下主要逻辑。判断任务的当前状态是否小于或者等于COMPLETING,也就是说,任务是NEW状态或者COMPLETING,调用awaitDone()方法,看下awaitDone()方法的实现,如下所示。

private int awaitDone(boolean timed, long nanos)
	throws InterruptedException {
	final long deadline = timed ? System.nanoTime() + nanos : 0L;
	WaitNode q = null;
	boolean queued = false;
	for (;;) {
		if (Thread.interrupted()) {
			removeWaiter(q);
			throw new InterruptedException();
		}

		int s = state;
		if (s > COMPLETING) {
			if (q != null)
				q.thread = null;
			return s;
		}
		else if (s == COMPLETING) // cannot time out yet
			Thread.yield();
		else if (q == null)
			q = new WaitNode();
		else if (!queued)
			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);
	}
}
复制代码

接下来,拆解awaitDone()方法。在awaitDone()方法中,最重要的就是for自旋循环,在循环中首先判断当前线程是否被中断,如果已经被中断,则调用removeWaiter()将当前线程从堆栈中移除,并且抛出InterruptedException异常,如下所示。

if (Thread.interrupted()) {
	removeWaiter(q);
	throw new InterruptedException();
}
复制代码

接下来,判断任务的当前状态是否完成,如果完成,并且堆栈句柄不为空,则将堆栈中的当前线程设置为空,返回当前任务的状态,如下所示。

int s = state;
if (s > COMPLETING) {
	if (q != null)
		q.thread = null;
	return s;
}
复制代码

当任务的状态为COMPLETING时,使当前线程让出CPU资源,如下所示。

else if (s == COMPLETING)
	Thread.yield();
复制代码

如果堆栈为空,则创建堆栈对象,如下所示。

else if (q == null)
	q = new WaitNode();
复制代码

如果queued变量为false,通过CAS操作为queued赋值,如果awaitDone()方法传递的timed参数为true,则计算超时时间,当时间已超时,则在堆栈中移除当前线程并返回任务状态,如下所示。如果未超时,则重置超时时间,如下所示。

else if (!queued)
	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);
复制代码

接下来,回到get()方法中,当awaitDone()方法返回结果,或者任务的状态不满足条件时,都会调用report()方法,并将当前任务的状态传递到report()方法中,并返回结果,如下所示。

return report(s);
复制代码

看来,这里还要看下report()方法啊,点进去看下report()方法的实现,如下所示。

private V report(int s) throws ExecutionException {
	Object x = outcome;
	if (s == NORMAL)
		return (V)x;
	if (s >= CANCELLED)
		throw new CancellationException();
	throw new ExecutionException((Throwable)x);
}
复制代码

可以看到,report()方法的实现比较简单,首先,将outcome数据赋值给x变量,接下来,主要是判断接收到的任务状态,如果状态为NORMAL,则将x强转为泛型类型返回;当任务的状态大于或者等于CANCELLED,也就是任务已经取消,则抛出CancellationException异常,其他情况则抛出ExecutionException异常。

至此,get()方法分析完成。注意:一定要理解get()方法的实现,因为get()方法是我们使用Future接口和FutureTask类时,使用的比较频繁的一个方法。

(6)set()方法与setException()方法

继续看FutureTask类的代码,接下来看到的是set()方法与setException()方法,如下所示。

protected void set(V v) {
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		outcome = v;
		UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
		finishCompletion();
	}
}

protected void setException(Throwable t) {
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		outcome = t;
		UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
		finishCompletion();
	}
}
复制代码

通过源码可以看出,set()方法与setException()方法整体逻辑几乎一样,只是在设置任务状态时一个将状态设置为NORMAL,一个将状态设置为EXCEPTIONAL。

至于finishCompletion()方法,前面已经分析过。

(7)run()方法与runAndReset()方法

接下来,就是run()方法了,run()方法的源代码如下所示。

public void run() {
	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 {
				result = c.call();
				ran = true;
			} catch (Throwable ex) {
				result = null;
				ran = false;
				setException(ex);
			}
			if (ran)
				set(result);
		}
	} finally {
		// runner must be non-null until state is settled to
		// prevent concurrent calls to run()
		runner = null;
		// state must be re-read after nulling runner to prevent
		// leaked interrupts
		int s = state;
		if (s >= INTERRUPTING)
			handlePossibleCancellationInterrupt(s);
	}
}
复制代码

可以这么说,只要使用了Future和FutureTask,就必然会调用run()方法来运行任务,掌握run()方法的流程是非常有必要的。在run()方法中,如果当前状态不是NEW,或者CAS操作返回的结果为false,则直接返回,不再执行后续逻辑,如下所示。

if (state != NEW ||
	!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
	return;
复制代码

接下来,在try代码块中,将成员变量callable赋值给一个临时变量c,判断临时变量不等于null,并且任务状态为NEW,则调用Callable接口的call()方法,并接收结果数据。并将ran变量设置为true。当程序抛出异常时,将接收结果的变量设置为null,ran变量设置为false,并且调用setException()方法将任务的状态设置为EXCEPTIONA。接下来,如果ran变量为true,则调用set()方法,如下所示。

try {
	Callable<V> c = callable;
	if (c != null && state == NEW) {
		V result;
		boolean ran;
		try {
			result = c.call();
			ran = true;
		} catch (Throwable ex) {
			result = null;
			ran = false;
			setException(ex);
		}
		if (ran)
			set(result);
	}
}
复制代码

接下来,程序会进入finally代码块中,如下所示。

finally {
	// runner must be non-null until state is settled to
	// prevent concurrent calls to run()
	runner = null;
	// state must be re-read after nulling runner to prevent
	// leaked interrupts
	int s = state;
	if (s >= INTERRUPTING)
		handlePossibleCancellationInterrupt(s);
}
复制代码

这里,将runner设置为null,如果任务的当前状态大于或者等于INTERRUPTING,也就是线程被中断了。则调用handlePossibleCancellationInterrupt()方法,接下来,看下handlePossibleCancellationInterrupt()方法的实现。

private void handlePossibleCancellationInterrupt(int s) {
	if (s == INTERRUPTING)
		while (state == INTERRUPTING)
			Thread.yield();
}
复制代码

可以看到,handlePossibleCancellationInterrupt()方法的实现比较简单,当任务的状态为INTERRUPTING时,使用while()循环,条件为当前任务状态为INTERRUPTING,将当前线程占用的CPU资源释放,也就是说,当任务运行完成后,释放线程所占用的资源。

runAndReset()方法的逻辑与run()差不多,只是runAndReset()方法会在finally代码块中将任务状态重置为NEW。runAndReset()方法的源代码如下所示,就不重复说明了。

protected boolean runAndReset() {
	if (state != NEW ||
		!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
		return false;
	boolean ran = false;
	int s = state;
	try {
		Callable<V> c = callable;
		if (c != null && s == NEW) {
			try {
				c.call(); // don't set result
				ran = true;
			} catch (Throwable ex) {
				setException(ex);
			}
		}
	} finally {
		// runner must be non-null until state is settled to
		// prevent concurrent calls to run()
		runner = null;
		// state must be re-read after nulling runner to prevent
		// leaked interrupts
		s = state;
		if (s >= INTERRUPTING)
			handlePossibleCancellationInterrupt(s);
	}
	return ran && s == NEW;
}
复制代码

(8)removeWaiter()方法

removeWaiter()方法中主要是使用自旋循环的方式来移除WaitNode中的线程,比较简单,如下所示。

private void removeWaiter(WaitNode node) {
	if (node != null) {
		node.thread = null;
		retry:
		for (;;) {          // restart on removeWaiter race
			for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
				s = q.next;
				if (q.thread != null)
					pred = q;
				else if (pred != null) {
					pred.next = s;
					if (pred.thread == null) // check for race
						continue retry;
				}
				else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
													  q, s))
					continue retry;
			}
			break;
		}
	}
}
复制代码

最后,在FutureTask类的最后,有如下代码。

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
static {
	try {
		UNSAFE = sun.misc.Unsafe.getUnsafe();
		Class<?> k = FutureTask.class;
		stateOffset = UNSAFE.objectFieldOffset
			(k.getDeclaredField("state"));
		runnerOffset = UNSAFE.objectFieldOffset
			(k.getDeclaredField("runner"));
		waitersOffset = UNSAFE.objectFieldOffset
			(k.getDeclaredField("waiters"));
	} catch (Exception e) {
		throw new Error(e);
	}
}
复制代码

关于这些代码的作用,会在后续深度解析CAS文章中详细说明,这里就不再探讨。

至此,关于Future接口和FutureTask类的源码就分析完了。

好了,今天就到这儿吧,我是冰河,我们下期见~~

Supongo que te gusta

Origin juejin.im/post/7078945914739490846
Recomendado
Clasificación