El uso y análisis de principios de FutureTask

En la introducción a la programación Java multiproceso y la creación de subprocesos, presentamos las formas de crear subprocesos, heredando respectivamente Thread, implementando la interfaz Runnable e implementando la interfaz Callable. En este artículo, presentamos otra forma de crear subprocesos, FutureTask. En comparación con la implementación de Runnable, no puede haber ningún resultado devuelto. FutureTask lo adapta para que pueda devolver un resultado predeterminado y sea compatible con la creación de subprocesos invocables. Aquí primero usamos un ejemplo para presentar el uso de FutureTask:

private static void futureTaskRunnable() throws InterruptedException, ExecutionException {
    //构造方法为一个接口Runnable实现和一个默认的返回值
    FutureTask<String> future = new FutureTask<String>(()->{
    }, "Hello,Word");
    //调用run()方法运行线程
    future.run();
    System.out.println("futureTaskRunnable: " + future.get());
}
	
private static void futureTaskcallable() throws InterruptedException, ExecutionException {
    //构造方法为接口 Callable的实现
    FutureTask<String> future = new FutureTask<String>(()->{
        return "test";
    });
    //调用run()方法运行线程
    future.run();
    System.out.println("futureTaskcallable: " + future.get());
}

Como en el código anterior, usamos los métodos Runnable y Callable para crear una FutureTask, y podemos llamar al método run () de FutureTask para iniciar el hilo. De acuerdo con el diagrama de jerarquía de clases de FutureTask, FutureTask no solo implementa la interfaz Future, sino que también implementa la interfaz Runnable. Por lo tanto, FutureTask se puede entregar al Ejecutor para su ejecución.

 Anteriormente dijimos que FutureTask adapta Runnable para que pueda devolver un valor predeterminado. Veamos cómo FutureTask se adapta a la interfaz Runnable. De hecho, este es un modo adaptador. A continuación, se muestra la implementación de FutureTask adaptando la interfaz Runnable.

 public FutureTask(Runnable runnable, V result) {
    //将Runnable接口封装成Callable接口,方法如下
    this.callable = Executors.callable(runnable, result);
    this.state = NEW; 
 }

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
       throw new NullPointerException();
    //适配器模式,下面我们看RunnableAdapter
    return new RunnableAdapter<T>(task, result);
}
//适配类
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;
    }
}

Después de introducir el uso de FutureTask, presentamos el principio de implementación de FutureTask. En JDK 1.6, Future usa AbstractQueuedSynchronizer. Muchas clases de herramientas relacionadas con la concurrencia en el paquete JUC usan la clase AQS. Se puede decir que es la piedra angular de las herramientas de concurrencia. Si no conoce esta clase, puede consultar el uso simple del sincronizador de cola AQS AbstractQueuedSynchronizer. AQS es un marco de sincronización que proporciona un mecanismo general para administrar atómicamente el estado de sincronización, bloquear y reactivar subprocesos y mantener la cola de subprocesos bloqueados. Cada sincronizador basado en AQS contendrá dos tipos de operaciones,

  • Al menos una operación de adquisición. Esta operación bloquea el hilo de llamada, a menos que el estado de AQS permita que este hilo continúe la ejecución. La operación de adquisición de FutureTask se llama método get () / get (timeout largo, unidad TimeUnit).
  • Al menos una operación de liberación. Esta operación cambia el estado de AQS y el estado cambiado permite desbloquear uno o más subprocesos bloqueados. La operación de lanzamiento de FutureTask incluye el método run () y el método cancel (...)

 La implementación específica no se presenta aquí. El uso de AQS es básicamente similar. En este blog, presentamos principalmente la implementación de Future in Task, que es equivalente a la complejidad de usar AQS. La implementación de FutureTask en JDK1.8 es más simple y clara. Partimos del método get (), el código fuente es el siguiente:

public V get() throws InterruptedException, ExecutionException {
    //获取当前FutureTask的状态
    int s = state;
    //如果未完成,加入到等待队列
    if (s <= COMPLETING)
       //加入到等待队列,等待线程执行完毕
       s = awaitDone(false, 0L);
    return report(s);
}

En el método get (), si la tarea no se completa, se agregará a la cola de espera para esperar a que se ejecute. Aquí vemos cómo la cola de espera en FutureTask es diferente a la de AQS. En comparación con la cola de espera en AQS, en FutureTask Más simple, su definición es la siguiente:

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

Veamos cómo FutureTask agrega el hilo actual a la cola de espera y espera a que vuelva otro hilo antes de continuar. Su lógica principal está en el método awaitDone (). Get () y get (tiempo de espera largo, unidad TimeUnit) finalmente se llaman El código fuente de este método es el siguiente:

//参数time 是否超时,nanos为毫秒数
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    //如果设置了超时,时间相加,否则为0
    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) // 任务正在执行,当前线程变为Ready状态,
            Thread.yield();
        else if (q == null)
            q = new WaitNode(); //创建一个WaitINGNode
        else if (!queued)
            CAS设置值
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        //有超时时间的处理
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
            }
            //调用LockSupport
            LockSupport.parkNanos(this, nanos);
        }
        else
            //调用LockSupport
            LockSupport.park(this);
    }
}

El código anterior es el proceso de agregar el hilo a la cola de espera. El hilo volverá a seguir ejecutando el hilo actual después de que se complete la ejecución de la tarea. A continuación, presentamos la lógica después de que la tarea se ejecuta o cancela, principalmente en el método run () y el método cancel. En, finalmente llama al método finishCompletion (), el código es el siguiente:

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;
                //调用unPart唤醒线程
                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
}

 

Supongo que te gusta

Origin blog.csdn.net/wk19920726/article/details/108724479
Recomendado
Clasificación