FutureTask源码解析

我们在讲源码之前先来一点最基本的应用吧,然后再慢慢的深入。

public class FutureExample1 {
    static class MyCallable implements Callable{
        @Override
        public String call() throws Exception {
            System.out.println("do something in callable");
            Thread.sleep(5000);
            return "done";
        }
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //用法一
        FutureTask futureTask = new FutureTask(new MyCallable());
        new Thread(futureTask).start();
        System.out.println(futureTask.get());

        //用法二
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future future = executorService.submit(new MyCallable());
        //如果线程没有执行完,会阻塞在这里
        System.out.println(future.get());
    }
}

从上面的例子可以得知FutureTask是一个同步工具类,它实现了Future接口,弄懂它有利于我们更加理解java异步操作的实现。

【1】FutureTask所使用的接口

  Runnable接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

 创建线程最主要的就是重写Runnable任务的一个run方法,这个run方法定义了该线程需要做什么事情,这里我们可以发现这个方法并没有返回值,如果我们需要拿到返回值的话,要怎么办呢?

从Runnable到Callable

@FunctionalInterface
public interface Callable {
    V call() throws Exception;
}

 Callable接口提供了一个call()方法作为线程的执行体,对比Callable接口与Runnable接口,最大的不同点在于:

1、Callable有返回值

2、Callable可以抛出异常

那么如何获取结果呢???

public static void main(String[] args) {
    Callable myCallable = () -> "This is the results.";
    try {
        String result = myCallable.call();
        System.out.println("Callable 执行的结果是: " + result);
    } catch (Exception e) {
        System.out.println("There is a exception.");
    }
}

简简单单的直接调用call方法就可以获取到执行结果了,但是呢这样有很大的弊端的,这样和直接调用一个方法有啥区别呢???调用call方法执行具体的操作,如果没执行完毕当前线程就会卡在这里了。这时我们会想到那可以直接使用Thread线程变量作为载体嘛,这样不就可以变成异步操作了嘛。可问题是Callable接口不是Runnable接口的子接口,所以Callable对象根本不能直接作为Thread的target,那么应该怎么调用call方法来得到返回结果呢???这时候FutureTask类就出现了哈哈哈,这个类实现了Runnable接口,可以作为Thread的target了,在Runnable的run方法内部再去调用Callable接口的call方法即可,而且该类又实现了Future接口,那么这个接口又是干嘛的呢?

Future接口

Future接口就是被设计用来获得异步操作的执行结果,它可以用来获取一个操作的执行结果,取消一个操作,判断一个操作是否已经完成或者是否已被取消。

public interface Future {
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
    
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    
    boolean isDone();
}

Future接口一共定义了5个方法:

get():该方法用来获取执行结果,如果任务还在执行中就阻塞等待。

get(long timeout, TimeUnit unit):该方法同get方法差不多,不同的是最多等待执行的时间,如果指定时间内任务没有完成则会抛出TimeoutException异常。

cancel(boolean mayInterruptIfRunning) :该方法用来尝试取消一个任务的执行,返回boolean类型表示取消操作是否成功。

isCancelled():该方法用于判断任务是否被取消了。如果一个任务在正常执行完成之前被cancel掉了,则返回true。

isDone():如果一个任务已经结束,则返回true。注意下面三种情况都是代表了任务结束:

【1】任务正常执行完毕

【2】任务抛出了异常

【3】任务已经被取消了

其实cancel操作返回true不一定代表任务真的就被取消了。这要取决于发动cancel方法时候任务所处的状态:

【1】如果发起cancel时任务还没开始运行,那么直接就被取消了,随后任务不会再执行了

【2】如果发起cancel时任务已经在运行的话,那么就取决于mayInterruptIfRunning参数了,如果mayInterruptIfRunning参数为true,则当前正在执行的任务被中断,如果为false则允许正在执行的任务继续执行,直到它执行完成。

RunnableFuture接口 

RunnableFuture接口其实就是同时继承了Runnnable接口和Future接口 

public interface RunnableFuture extends Runnable, Future {
    void run(); 
}

 而恰恰好FutureTask就是实现了该接口,也就是说FutureTask实现了Runnable接口一方面可以作为任务体执行,另一方面它又实现了Future接口可以来获取获取操作的执行结果,或者判断任务是否执行完毕,取消任务的执行等等。所以简单点说,FutureTask本质上就是一个Task,我们可以把它当做一个Runnable对象来使用就行了。但是它同时又实现了Future接口,因此我们可以进行一些额外的操作。

接下来我们开始分析FutureTask源码,将从三个方面为切入点进行分析源码:状态、队列、CAS

    //状态
    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;

    private Callable callable;
    
    private Object outcome; 
    
    private volatile Thread runner;
    //队列
    private volatile WaitNode waiters;

    //CAS操作
    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);
        }
    }

【1】state:状态属性表示的且是volatile类型的,确保了不同线程对它修改的可见性。state属性是贯穿了整个FutureTask的最核心属性,该属性的值代表了任务在运行过程中的状态,随着任务的执行,状态不断的进行转变。七个状态包含了1个初始化状态,2个中间态以及4个 终止态。

                   state of FutureTask

任务的初始化状态是NEW,这个在构造函数初始化的时候就设置了。

任务的终止状态有4种:

NORMAL : 任务正常执行完毕

EXCEPTIONAL : 任务执行过程种发生异常

CANCELLED : 任务被取消

INTERRUPTED : 任务被中断

任务的中间态有2种:

COMPLETING : 正在设置任务结果

INTERRUPTING : 正在中断运行任务的线程

这里要切记其实中间态只是一瞬间而已,非常的短暂。而且中间态不代表任务正在执行中,而是任务已经执行完毕了,正在设置执行的返回结果,也就是说只要state不处于NEW状态的话,就说明任务已经执行完毕了。

将一个任务的状态设置为终止态有以下三种方法:

1、set 

2、setExcetion

3、cancel

我们后面分析源码的时候会讲到的。

【2】callable : 代表了要执行的任务本身也就是FutureTask的task部分,这里之所以使用callable而不使用runnable是因为FutureTask实现了Future接口,后面要获取执行的结果,所以就使用了callable。

【3】outcome : 属性代表了任务的执行结果或者抛出的异常,所以该类型为Object类型。通常就是FutureTask的run方法调用call()方法返回结果设置到outcome属性中,这样我们就可以获取到了。

【4】runner : 代表了当前执行FutureTask中任务的线程,为什么需要记录该线程呢???这是为了中断获取cancel任务的时候可以用到,只有知道了执行该任务的线程是哪个,我们才可以去中断它呢。

【5】waiters : 本质上就是一个单向链表的队列,它表示所有正在等待任务执行完毕的一个线程集合,也就是这些线程都是在等待任务执行完毕。如果我们去get()获取结果的时候,当前任务还没有执行完毕怎么办呢???那么获取结果的线程就会在等待队列中被挂起,直到任务执行完毕后被唤醒。接下来我们看下每个节点的一个结构:

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

 其实挺简单的,它就包含了记录线程的thread属性和指向下一个节点的next属性。它其实是当做栈来使用的,并使用CAS来完成入栈和出栈操作。那么为啥要使用一个线程安全的栈呢?因为同一时刻可能有多个线程在获取任务的执行结果,结果任务还在执行中,那么我们此时肯定就要把这些线程封装成waitNode结果然后扔到栈中,也就是完成入栈操作。当然的也可能出现多个线程同时出栈的情况,因此需要使用CAS操作来保证入栈以及出栈的线程安全。这里FutureTask只需要使用一个waiters属性即可,也就是一个指向栈顶的指针就行,其实就是链表的头结点。

                                  Treiber stack

 【6】CAS操作:一般我们在静态代码块中就需要初始化CAS操作的属性的偏移量,从上面代码中得知,CAS操作主要针对3个属性 : runner、state、waiters,说明这3个属性基本是会被多个线程同时访问的,所以必须保证线程安全性。

那么,要怎么获得目标field的内存偏移量offset呢? Unsafe类为我们提供了一个方法:

public native long objectFieldOffset(Field field);

该方法的参数是我们进行CAS操作的field对象,那么怎么获取这个field对象呢?最直接的办法就是通过反射

Class k = FutureTask.class;

Field stateField = k.getDeclaredField("state");

这样我们就可以直接对FutureTask的state属性进行操作了。

 

分析完了上面的基本属性,我们接下来看看FutureTask的构造函数:

public FutureTask(Callable 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
}

 FutureTask一共有两个构造函数,这两个构造函数一个直接传入callable对象,一个是传入Runnable对象以及一个result结果,然后通过Executors工具类将它适配为Callable对象,所以这两个构造函数基本是一样的。

接口实现

【1】run方法(实现了Runnable接口的run方法) 

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                               null, Thread.currentThread()))
            return;
        try {
            Callable 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);
        }
    }

首先我们判断当前状态是不是NEW,如果不为NEW的话表示当前任务已经执行完毕,那么直接return。如果当前状态为NEW的话则使用CAS操作将runner属性设置为当前线程,也就是记录执行当前任务的线程,compareAndSwapObject也就是cas操作。接下来我们就调用了Callable对象的call方法来执行任务,如果任务执行成功,则调用set(result)来设置结果,否则使用setException(ex)来设置抛出的异常。

接下来我们看下set(result)方法:

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

该方法表示已经执行完成了,然后开始使用CAS操作设置当前的state属性由原来的NEW状态为COMPLETING状态,其实该状态是一个非常短暂的中间状态,表示正在设置执行结果。状态设置成功后,就把任务结果赋值给outcome,然后再把state属性变为NORMAL状态,这里没有比较设置哦,是直接进行设置。由于state属性被设为volatile,所以保证了state状态对其他线程的可见性。然后调用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
    }

 首先看该方法其实就是把waiters属性由原来的值设置为null,在前面可以得知waiters是指向栈的顶节点,现在将它置为null的目的就是清空整个栈。如果设置不成功也就是if语句不会被执行,那么就继续下一次循环,而下一次循环的条件是waiters!=null,也就是当前的waiters还不会被其他线程设置为Null,那么我们继续执行CAS操作直到成功为止。其实这个for循环只是为了确保waiters被成功设置为null后可以跳出循环而已,也就是我们经常说的自旋操作。将waiters属性设置为null之后就进行for(;;)循环了,这才是真正的遍历节点,也就是普通的遍历链表的操作,我们前面有讲到了waitNode存放了当前等待执行任务结束的线程,这个循环的作用是当前任务已经执行完毕了,遍历所有等待的线程并唤醒他们。

然后就执行done()方法,这个方法是空方法,留给子类去覆写的,以实现一些任务执行结束前的额外操作,然后就是把callable属性也清空了。

最后我们又回到了run方法来了,run方法最后执行了finally模块,首先把runner属性设置为null,然后这时候检查有没有遗漏的中断呢??如果发现s >= interrupting,说明执行任务的线程有可能被中断了,因为s >= interrupting只有两种可能,state状态为interrupting以及Interrupted了。那么我们不仅会想了我们前面不是已经set方法或者setException方法将state状态设置为NORMAL或者EXCEPTIONAL了吗?怎么还会出现中断状态呢??要切记我们在多线程的环境中哦,也许当前线程执行run方法的时候,此时还没有执行set()方法哦,然后其他线程取消了任务的执行,也就是把state状态改写为interrupting或者Interrupted状态了,那么此时当前线程执行完的时候再去run()方法的时候就会发觉CAS操作根据设置不了了,因为当前的state预期值已经不是NEW状态

UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)

关于cancel方法我们在后面再进行讲解吧。我们还是回到当前代码中来,如果当前状态为中断状态呢,那么我们就执行以下方法:

private void handlePossibleCancellationInterrupt(int s) {
    // It is possible for our interrupter to stall before getting a
    // chance to interrupt us.  Let's spin-wait patiently.
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt
}

判断当前state状态如果为中断中间态,那么不要马上就退出run方法,而是应该等待状态变为中断终止态才代表执行完毕可以退出方法,上面是使用了让出当前线程然后继续原地自旋做判断。

总结:

  1. 将runner属性设置成当前正在执行run方法的线程
  2. 调用callable成员变量的call方法来执行任务
  3. 设置执行结果outcome, 如果执行成功, 则outcome保存的就是执行结果;如果执行过程中发生了异常, 则outcome中保存的就是异常,设置结果之前,先将state状态设为中间态
  4. 对outcome的赋值完成后,设置state状态为终止态(NORMAL或者EXCEPTIONAL)
  5. 唤醒Treiber栈中所有等待的线程
  6. 善后清理(waiters, callable,runner设为null)
  7. 检查是否有遗漏的中断,如果有,等待中断状态完成。

【2】cancel(boolean mayInterruptlfRunning)

分析了上面的run方法,那么我们紧接着就来分析取消任务的方法,看看一个任务是怎么被别的线程取消的。

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;
    }

 首先判断当前的状态是否为NEW,如果不为NEW说明已经被执行完毕或者已经取消了,那么直接返回false。

如果此时状态还为NEW的话,那么我们接着做CAS操作,UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED),也就是根据mayInterruptIfRunning的值将state状态设置为INTERRUPTING或者CANCELLED状态,然后就可以执行后面的try语句了,但最后无论如何都是返回true(无论是否取消了任务)。

我们继续往下看try方法做了什么操作呢?

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();
}

就是判断当前mayInterruptIfRunning参数是否为true,来确定下一步如何操作

1、如果此时为false也就是说此时状态为CANCELED,那么无需做任何操作,程序继续往下执行

2、如果此时为false也就是说此时状态为INTERRUPTING,最后变为INTERRUPTED。

对于第一种情况,虽然cancel方法最终返回了true,但它只是简单的将state状态设置为CANCELED而已,并没有中断线程的执行。但是这样带来的后果就是,任务即使执行完毕了,也无法去设置任务的执行结果(set方法)。很简单我们之前在分析run方法的时候使用set方法来设置结果,但是采用的是CAS操作,而且当前的状态必须为NEW状态,如果不为NEW则不设置中间态状态了,也就设置不了结果值了。

对于第二种情况,我们会设置runner(当前执行该任务的线程)中断标志位,但是该线程响应不响应呢我们是控制不了的,只能由执行任务的线程自己决定,也就是重写了call()方法的具体执行体决定的,程序员自己决定是否对中断标志位进行响应,是否抛出中断异常。在FutureTask的run方法中有对抛出异常的情况做处理的并调用setException()方法,这里中断异常又分成了两种情况:

1、cancel操作,然后用户在call方法里面响应中断后抛出异常,这时catch (Throwable ex){}代码块会捕捉异常并且执行setException(ex)操作,但是这个操作肯定是失败的,因为是采用了CAS操作,并且前提是当前状态为NEW:

if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) 

所以在这里最大的意义不是为了对中断进行处理,而是让出资源而已。

2、自己在call方法中正常执行失败而抛出的异常,此时state状态仍为NEW,则这个异常会被设置到outcome中。

反正无论如何FutureTask的run方法都会执行到finally方法块,这时候如果发现s == interrupting,说明cancel方法还没设置为终止状态,那么就必须等待直到为中断终止态。这样子cancel方法和run方法的分析对接上了。

【3】get方法 获取执行结果

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

当前任务还没有执行完毕或者正在设置执行结果的话,我们就使用awaitDone方法等待任务直到其变为终止状态,这里注意awaitDone方法返回值是任务的状态而不是结果,最后我们再根据状态来计算结果。接下来我们看下awaitDone源码:

在分析源码之前有一点我们先说明一下,FutureTask中会涉及到两种线程,一种是执行任务的线程,它只有一个,FutureTask类的run方法就是由它执行的。另一种线程是专门获取任务执行结果的,它可以有很多个,这些线程都是可并发执行的,都可以调用get方法来获取任务的执行结果。但是如果当前任务还没有执行完的话,这些线程就需要进入waiterNode栈中进行挂起,直到任务执行结束,或者任务被中断。

    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        //自循环
        for (;;) {
            //首先检测线程是否被设置中断标志位,如果等待的任务还没有执行完的话则调用get方法会被    
            //阻塞并扔到waiters栈中挂起等待。如果任务迟迟没有执行完毕,可以直接中断在waiters栈 
            //中的线程
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            //如果任务已经进入终止状态(s > COMPLETING)的话,置当前的waitNode的线程为null,然    
            //后返回当前任务的状态
            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            //否则如果任务正在设置执行结果,我们就让出CPU的资源继续等待
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            //再否则就说明线程还没有执行完成,判断此时q是否为null,如果为空说明当前线程还没有入 
            //栈等待队列,那么我们新建一个waitNode节点,也就是记录当前线程的一个节点
            else if (q == null)
                q = new WaitNode();
            //如果q不为null了,说明waiterNode节点被创建出来了。如果queued为false,说明此时还没            
            //有入队,下面CAS操作是将新建的q节点添加到原来waiters的头结点之前,也就是入栈操作。
            else if (!queued)
                //q.next = waiters; 当前节点的next节点指向当前的栈顶元素
                //期望值就是如果当前的栈顶节点在这个过程没有变的话,既没有发生并发入栈情况
                //那么就执行最终值,也就是waiters = q,也就是waiters指针指向了q节点。
                //保证了同一时刻如果有多个线程在同时入栈,只有一个能够操作成功。
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            //如果上面的条件还是不满足的话,那么判断此时是否设置timed,也就是超时限制
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            //若是执行到这一步了,那么就是把获取执行结果的线程挂起了(挂在了栈中)
            else
                LockSupport.park(this);
        }
    }

那么什么时候这个线程才会被唤醒呢??

1、任务执行完毕之后,在finishCompletion方法中会唤醒所有在栈中等待的线程

2、等待的线程自身因为被中断等原因而被唤醒 

然后如果此时被唤醒的话,会继续下一轮的循环,首先是检查中断,此时q已经不为null了,因此在有中断发生的情况下,抛出中断之前还多了一步removeWaiter(q)操作,该操作是将当前线程从等待的栈中移除,相比入栈操作出栈稍微复杂一点:

private void removeWaiter(WaitNode node) {
    if (node != null) {
        //首先设置要出栈的waitNode节点为NULL,其实这里相当于一个标记而已
        node.thread = null;
        retry:
        for (;;) {          // restart on removeWaiter race
            for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                //s为头结点的下一个节点
                s = q.next;
                //判断当前节点是否不为空(null就是移除的标记)
                if (q.thread != null)
                    pred = q;
                //如果q为NULL,也就是该节点将会被移除的,此时判断pred是否为空,如果第一次循环的    
                //话,pred为NULL的不成立,如果第二次以后循环的话pred不为Null,该条件成立
                else if (pred != null) {
                    //此时就把pred节点(当前节点的pre节点)指向s节点(当前节点的next节点),也 
                    //就是删除了p节点了
                    pred.next = s;
                    //这里如果pred的Thread(volatile修饰,保证可见性)为null,表明有可能这个 
                    //pred节点可能被其他线程标志了,将会被拿出waiters链表,那么我们此时不必要 
                    //再循环下去了,而是重头来了
                    if (pred.thread == null) // check for race
                        continue retry;
                }
                //如果刚好要删除的节点位于栈顶,那么直接waiters指向s节点,如果操作不成功的话,        
                //会回到retry处重来,即使成功了程序依旧会遍历完整个节点,把node.thread为Null的 
                //全部删除掉
                else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
                    continue retry;
            }
            break;
        }
    }
}

我们可以看到上面虽然我们传入了一个需要剔除的节点,但事实上它可能剔除的不止是传入的节点,而是所有已经被标记为null的节点都会被剔除,这样不仅清除容易(不需要专门去定位传入的node在哪里),也提升了效率(一下子清除掉所有被标记的节点)。

我们回到awaitdone方法上面来,如果此时线程不是因为中断而被唤醒,那么则会继续往下执行,此时q不为null了,queued为true了,不需要再入栈了。否则就是原地继续自旋等待(s==COMPLETING的时候是调用Thread.yield或者由于对应任务还没有执行完成的情况下直接被挂起),否则就等到执行执行完毕会唤醒挂起的线程。至此get方法就分析完毕了。为什么awaitDone方法和get方法都没有加锁呢,它不怕产生线程安全问题吗??其实整个方法内部都使用了局部变量,没有线程安全问题的。对于全局共享的变量waiters的修改,也会使用CAS操作,保证了线程安全,而state属性本身是volatile修饰的,保证了读取的时候可见性,所以不用加锁也是安全的。

你可能感兴趣的:(并发编程)