L'utilisation et l'analyse des principes de FutureTask

Dans l'introduction à la programmation multithread Java et à la création de threads, nous avons présenté les moyens de créer des threads, respectivement en héritant de Thread, en implémentant l'interface Runnable et en implémentant l'interface Callable. Dans cet article, nous présentons une autre façon de créer des threads, FutureTask. Par rapport à l'implémentation de Runnable, il ne peut y avoir de résultat de retour. FutureTask l'adapte pour qu'il puisse renvoyer un résultat par défaut et prend en charge la création de threads Callable. Ici, nous utilisons d'abord un exemple pour présenter l'utilisation 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());
}

Comme dans le code ci-dessus, nous utilisons les méthodes Runnable et Callable pour créer un FutureTask, et nous pouvons appeler la méthode run () de FutureTask pour démarrer le thread. Selon le diagramme de hiérarchie de classes de FutureTask, FutureTask implémente non seulement l'interface Future, mais implémente également l'interface Runnable. Par conséquent, FutureTask peut être remis à Executor pour exécution.

 Nous avons dit précédemment que FutureTask adapte Runnable afin qu'il puisse renvoyer une valeur par défaut. Voyons comment FutureTask s'adapte à l'interface Runnable. En fait, il s'agit d'un mode adaptateur. Ce qui suit est l'implémentation de FutureTask adaptant l'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;
    }
}

Après avoir introduit l'utilisation de FutureTask, nous introduisons ensuite le principe de mise en œuvre de FutureTask. Dans JDK 1.6, Future utilise AbstractQueuedSynchronizer. De nombreuses classes d'outils liées à la concurrence dans le package JUC utilisent la classe AQS. On peut dire que c'est la pierre angulaire des outils de concurrence. Si vous ne connaissez pas cette classe, vous pouvez vous référer à l'utilisation simple du synchroniseur de files d'attente AQS AbstractQueuedSynchronizer. AQS est un cadre de synchronisation qui fournit un mécanisme général pour gérer de manière atomique l'état de synchronisation, bloquer et réveiller les threads et maintenir la file d'attente des threads bloqués. Chaque synchroniseur basé sur AQS contiendra deux types d'opérations,

  • Au moins une opération d'acquisition. Cette opération bloque le thread appelant, sauf si l'état d'AQS autorise ce thread à continuer l'exécution. L'opération d'acquisition de FutureTask est appelée méthode get () / get (long timeout, unité TimeUnit).
  • Au moins une opération de libération. Cette opération modifie l'état d'AQS et l'état modifié permet à un ou plusieurs threads bloqués d'être débloqués. L'opération release de FutureTask inclut la méthode run () et la méthode cancel (...)

 Son implémentation spécifique n'est pas présentée ici. L'utilisation d'AQS est fondamentalement similaire. Dans ce blog, nous présentons principalement l'implémentation de Future in Task, qui équivaut à la complexité de l'utilisation d'AQS. L'implémentation de FutureTask dans JDK1.8 est plus simple et plus claire. On part de la méthode get (), le code source est le suivant:

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

Dans la méthode get (), si la tâche n'est pas terminée, elle sera ajoutée à la file d'attente pour attendre que la tâche soit exécutée. Ici, nous voyons en quoi la file d'attente dans FutureTask est différente de celle dans AQS. Par rapport à la file d'attente dans AQS, dans FutureTask Plus simple, sa définition est la suivante:

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

Voyons comment FutureTask ajoute le thread actuel à la file d'attente et attend le retour d'un autre thread avant de continuer. Sa logique principale est dans la méthode awaitDone (). Get () et get (long timeout, unité TimeUnit) sont finalement appelés Le code source de cette méthode est le suivant:

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

Le code ci-dessus est le processus d'ajout du thread à la file d'attente. Le thread retournera pour continuer à exécuter le thread actuel une fois l'exécution de la tâche terminée. Ci-dessous, nous présentons la logique après l'exécution ou l'annulation de la tâche, principalement dans la méthode run () et la méthode cancel Dans, il appelle enfin la méthode finishCompletion (), le code est le suivant:

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
}

 

Je suppose que tu aimes

Origine blog.csdn.net/wk19920726/article/details/108724479
conseillé
Classement