O uso e análise de princípio de FutureTask

Na introdução à programação Java multithread e à criação de threads, apresentamos as maneiras de criar threads, respectivamente herdando Thread, implementando a interface Runnable e implementando a interface Callable.Neste artigo, apresentamos outra maneira de criar threads, FutureTask. Em comparação com a implementação de Runnable, não pode haver nenhum resultado de retorno. FutureTask adapta-o para que possa retornar um resultado padrão e oferece suporte à criação de threads de chamada. Aqui, primeiro usamos um exemplo para apresentar o 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 no código acima, usamos os métodos Runnable e Callable para criar um FutureTask e podemos chamar o método run () de FutureTask para iniciar o thread. De acordo com o diagrama de hierarquia de classes de FutureTask, FutureTask não apenas implementa a interface Future, mas também implementa a interface Runnable. Portanto, FutureTask pode ser entregue ao Executor para execução.

 Anteriormente, dissemos que FutureTask adapta Runnable para que possa retornar um valor padrão. Vejamos como FutureTask se adapta à interface Runnable. Na verdade, este é um modo de adaptador. A seguir está a implementação de FutureTask adaptando interface 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;
    }
}

Depois de introduzir o uso de FutureTask, apresentamos o princípio de implementação de FutureTask. No JDK 1.6, Future usa AbstractQueuedSynchronizer. Muitas classes de ferramentas relacionadas à concorrência no pacote JUC usam a classe AQS. Pode-se dizer que é a base das ferramentas de simultaneidade. Se você não conhece esta classe, pode consultar o uso simples do sincronizador de fila AQS AbstractQueuedSynchronizer. AQS é uma estrutura de sincronização que fornece um mecanismo geral para gerenciar atomicamente o estado de sincronização, bloquear e ativar threads e manter a fila de threads bloqueados. Cada sincronizador baseado em AQS conterá dois tipos de operações,

  • Pelo menos uma operação de aquisição. Esta operação bloqueia o encadeamento de chamada, a menos que o estado de AQS permita que esse encadeamento continue a execução. A operação de aquisição de FutureTask é chamada de método get () / get (tempo limite longo, unidade TimeUnit).
  • Pelo menos uma operação de liberação. Esta operação altera o estado de AQS e o estado alterado permite que um ou mais threads bloqueados sejam desbloqueados. A operação de liberação de FutureTask inclui o método run () e o método cancel (...)

 A implementação específica não é apresentada aqui. O uso de AQS é basicamente semelhante. Neste blog, apresentamos principalmente a implementação de Future in Task, que é equivalente à complexidade de usar AQS. A implementação de FutureTask em JDK 1.8 é mais simples e clara. Começamos com o método get (), o código-fonte é o seguinte:

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

No método get (), se a tarefa não tiver sido executada, ela será adicionada à fila de espera para aguardar a execução da tarefa. Aqui vemos como a fila de espera em FutureTask é diferente daquela em AQS. Comparada com a fila de espera em AQS, em FutureTask Mais simples, sua definição é a seguinte:

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

Vamos ver como FutureTask adiciona a thread atual à fila de espera e espera que outra thread retorne antes de continuar. Sua lógica principal está no método awaitDone (), get () e get (long timeout, unidade TimeUnit) são finalmente chamados O código-fonte deste método é o seguinte:

//参数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);
    }
}

O código acima é o processo de adicionar o encadeamento à fila de espera. O encadeamento retornará para continuar executando o encadeamento atual após a execução da tarefa ser concluída. A seguir, apresentamos a lógica após a tarefa ser executada ou cancelada, principalmente no método run () e no método cancel , Finalmente chama o método finishCompletion (), o código é o seguinte:

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
}

 

Acho que você gosta

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