多线程与锁(5):LockSupport

一、概念和基础

LockSupport位于java.util.concurrent.locks包下,是线程的阻塞原语,用来阻塞线程和唤醒线程。每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用 unpark 使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。

LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程。主要是通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作的。,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

每个线程都有一个许可(permit),permit只有两个值1和0,默认是0。

二、LockSupport API

1. LockSupport函数列表

// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)

// 为了线程调度,禁用当前线程,除非许可可用。
static void park()

// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)

// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)

// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)

// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)

// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)

// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)

2. park()和park(Object blocker)方法区别

JDK1.6引入对应的拥有Blocker版本。

实际上LockSupport阻塞和唤醒线程的功能是依赖于sun.misc.Unsafe,这是一个很底层的类。比如park()方法的功能实现则是靠unsafe.park()方法。另外在阻塞线程这一系列方法中还有一个很有意思的现象就是,每个方法都会新增一个带有Object的阻塞对象的重载方法。那么增加了一个Object对象的入参会有什么不同的地方了?示例代码很简单就不说了,直接看dump线程的信息。

调用park()方法dump线程:
案例代码:

public class LockSupportWaitForObjectDemo {
    public static void main(String[] args)throws Exception {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                //阻塞挂起
                LockSupport.park();
                System.out.println(sum);
            }
        });
        A.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
    }
}

运行,cmd下jps 查看所有进程,查到进程PID,然后根据进程PID导出线程堆栈:

// 查看所有进程 可知进程id为16888
C:\Users\nowuseeme>jps
9420 AppMain
15884 Jps
//导出线程堆栈
C:\Users\nowuseeme>jstack 9420>>myjstack.txt

查看myjstack.txt,可得:

"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x0000000002388000 nid=0x4594 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000058b86000 nid=0x4758 waiting on condition [0x0000000059a7e000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
	at com.test.example04.LockSupportWaitForObjectDemo$1.run(LockSupportWaitForObjectDemo.java:25)
	at java.lang.Thread.run(Thread.java:745)

调用park(Object blocker)方法dump线程 :
改下上面的案例代码,调整后为:

public class LockSupportWaitForObjectDemo {

    //定义一个内部类作为park(Object blocker)的参数blocker
    static class InnerClass{
    }
    public static final InnerClass innerClass = new InnerClass();
    public static void main(String[] args)throws Exception {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                //阻塞挂起
                LockSupport.park(innerClass);
                System.out.println(sum);
            }
        });
        A.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
    }
}

运行,cmd下jps 查看所有进程,查到进程PID,然后根据进程PID导出线程堆栈:

// 查看所有进程 可知进程id为16888
C:\Users\nowuseeme>jps
16888 AppMain
15884 Jps
//导出线程堆栈
C:\Users\nowuseeme>jstack 16888>>myjstack1.txt

堆栈如下:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.5-b02 mixed mode):

"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x0000000002388000 nid=0x3c10 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000058a7a000 nid=0x40b4 waiting on condition [0x0000000059b1e000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000d5c425e8> (a com.test.example04.LockSupportWaitForObjectDemo$InnerClass)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at com.test.example04.LockSupportWaitForObjectDemo$1.run(LockSupportWaitForObjectDemo.java:25)
	at java.lang.Thread.run(Thread.java:745)

通过分别调用这两个方法然后dump线程信息可以看出,带Object的park方法相较于无参的park方法会增加parking to wait for <0x00000000d5c425e8> (a com.test.example04.LockSupportWaitForObjectDemo$InnerClass) 的信息,这种信息就类似于记录“案发现场”,有助于工程人员能够迅速发现问题解决问题。

有个有意思的事情是,我们都知道如果使用synchronzed阻塞了线程dump线程时都会有阻塞对象的描述,在java 5推出LockSupport时遗漏了这一点,在java 6时进行了补充。还有一点需要需要的是:synchronzed致使线程阻塞,线程会进入到BLOCKED状态,而调用LockSupprt方法阻塞线程会致使线程进入到WAITING状态。

三、LockSupport的park()/unpark() 和 Object的wait()/notify()比较

JDK1.8后,ReentrantLock及ReentrantReadWriteLock是基于AQS实现的,AQS内部使用了unsafe类进行操作;LockSupport也是基于unsafe类操作。可以说LockSupport也是阻塞的,但是不会发生Thread.suspend 和 Thread.resume所可能引发的死锁问题。而AQS是非阻塞机制。LockSupport.park()和unpark(),与object.wait()和notify()的区别? 主要的区别应该说是它们面向的对象不同。阻塞和唤醒是对于线程来说的,LockSupport的park/unpark更符合这个语义,以“线程”作为方法的参数, 语义更清晰,使用起来也更方便。而wait/notify的实现使得“阻塞/唤醒对线程本身来说是被动的,要准确的控制哪个线程、什么时候阻塞/唤醒很困难, 要不随机唤醒一个线程(notify)要不唤醒所有的(notifyAll)。

park函数是将当前调用Thread阻塞,而unpark函数则是将指定线程Thread唤醒。

与Object类的wait/notify机制相比,park/unpark有两个优点:

1. LockSupport更加简单

代码为例:
线程A执行一段业务逻辑后调用wait阻塞住自己。主线程调用notify方法唤醒线程A,线程A然后打印自己执行的结果。

public class ObjectWaitDemo {
    public static void main(String[] args)throws Exception {
        final Object obj = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                try {
                    obj.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(sum);
            }
        });
        A.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
        obj.notify();
    }
}

执行这段代码,会发生以下错误:

ava.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.test.example04.ObjectWaitDemo$1.run(ObjectWaitDemo.java:21)
	at java.lang.Thread.run(Thread.java:745)
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.test.example04.ObjectWaitDemo.main(ObjectWaitDemo.java:31)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

原因很简单,wait和notify/notifyAll方法只能在同步代码块里用,将代码修改为如下就可正常运行了:

public class ObjectWaitDemo {

    public static void main(String[] args)throws Exception {
        final Object obj = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {

                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }

                try {
                    //wait前添加同步代码块
                    synchronized (obj) {
                        obj.wait();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(sum);

            }
        });
        A.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
        //notify前添加同步代码块
        synchronized (obj) {
            obj.notify();
        }
    }
}

如果换成LockSupport,则操作会简单很多,还是上面的案例,改造如下:

public class LockSupportWaitDemo {

    public static void main(String[] args)throws Exception {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                LockSupport.park();
                System.out.println(sum);
            }
        });
        A.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
        LockSupport.unpark(A);
    }
}

直接调用即可,不需要在同步块中调用。

2. LockSupport更加灵活

LockSupport的灵活性体现在两个方面:
(1) 以thread为操作对象更符合阻塞线程的直观定义操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

(2) unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。 相对而言Object的notify必须在wait方法调用之后,才能调用,不然会抛异常。
还是以上面的

public class ObjectWaitNormalDemo {

    public static void main(String[] args)throws Exception {
        final Object obj = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }

                try {
                    synchronized (obj) {
                        obj.wait();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(sum);

            }
        });
        A.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        //Thread.sleep(1000);
        synchronized (obj) {
            obj.notify();
        }
    }
}

3. demo

(1) 先interrupt再park

public class LockSupportDemo{

    public static void main(String[] args) throws InterruptedException {
        String blocker = new String("MyLockSupportDemo");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {               
                System.out.println("睡觉");
                LockSupport.park(blocker);
                System.out.println("起床");
                System.out.println("是否中断:" + Thread.currentThread().isInterrupted());
            }
        });
        t.setName("DailyLife");
        t.start();
        t.interrupt();
        System.out.println("做噩梦");
    }
}

中断后执行park会直接执行下面的方法,并不会抛出InterruptedException,输出结果如下:

做噩梦
睡觉
起床
是否中断:true

(2) 先unpark一次再执行park

public class LockSupportDemo1 {

    public static void main(String[] args) throws InterruptedException {
        String blocker = new String("MyLockSupportDemo");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("睡觉");
                LockSupport.park(blocker);
                System.out.println("起床");
            }
        });
        t.setName("DailyLife");
        t.start();
        LockSupport.unpark(t);
        System.out.println("提前上好闹钟");
    }
}

按照上面说过的,先设置好许可(unpark)再获取许可的时候不会进行等待,正如我们说的那样输出如下:

提前上好闹钟
睡觉
起床

四 源码与深入解析

1. 源码解析

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.

    private static void setBlocker(Thread t, Object arg) {
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }
    
    /**
     * 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象。
     * 如果该调用不受阻塞,则返回 null。
     * 返回的值只是一个瞬间快照,即由于未解除阻塞或者在不同的 blocker 对象上受阻而具有的线程。
     */
    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }
    
    /**
     * 如果给定线程的许可尚不可用,则使其可用。
     * 如果线程在 park 上受阻塞,则它将解除其阻塞状态。
     * 否则,保证下一次调用 park 不会受阻塞。
     * 如果给定线程尚未启动,则无法保证此操作有任何效果。 
     * @param thread: 要执行 unpark 操作的线程;该参数为 null 表示此操作没有任何效果。
     */
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    /**
     * 为了线程调度,在许可可用之前阻塞当前线程。 
     * 如果许可可用,则使用该许可,并且该调用立即返回;
     * 否则,为线程调度禁用当前线程,并在发生以下三种情况之一以前,使其处于休眠状态:
     *  1. 其他某个线程将当前线程作为目标调用 unpark
     *  2. 其他某个线程中断当前线程
     *  3. 该调用不合逻辑地(即毫无理由地)返回
     */
    public static void park() {
        UNSAFE.park(false, 0L);
    }

    /**
     * 和park()方法类似,不过增加了等待的相对时间
     */
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

    /**
     * 和park()方法类似,不过增加了等待的绝对时间
     */
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }
    
    /**
     * 和park()方法类似,只不过增加了暂停的同步对象
     * @param blocker 导致此线程暂停的同步对象
     * @since 1.6
     */
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    
    /**
     * parkNanos(long nanos)方法类似,只不过增加了暂停的同步对象
     * @param blocker 导致此线程暂停的同步对象
     * @since 1.6
     */
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }
    
    /**
     * parkUntil(long deadline)方法类似,只不过增加了暂停的同步对象
     * @param blocker 导致此线程暂停的同步对象
     * @since 1.6
     */
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

    static final int nextSecondarySeed() {
        int r;
        Thread t = Thread.currentThread();
        if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
            r ^= r << 13;   // xorshift
            r ^= r >>> 17;
            r ^= r << 5;
        }
        else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
            r = 1; // avoid zero
        UNSAFE.putInt(t, SECONDARY, r);
        return r;
    }

    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

2. Unsafe的park和unpark

LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:

/**
 * 为指定线程提供“许可(permit)”
 */
public native void unpark(Thread jthread);

/**
 * 阻塞指定时间等待“许可”。
 * @param isAbsolute: 时间是绝对的,还是相对的
 * @param time:等待许可的时间
 */
public native void park(boolean isAbsolute, long time);  

上面的这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。
注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。

深入HotSpot的源码来看看。每个java线程都有一个Parker实例,Parker类是这样定义的:

class Parker : public os::PlatformParker {  
private:  
  volatile int _counter ;  
  ...  
public:  
  void park(bool isAbsolute, jlong time);  
  void unpark();  
  ...  
}  
class PlatformParker : public CHeapObj<mtInternal> {  
  protected:  
    pthread_mutex_t _mutex [1] ;  
    pthread_cond_t  _cond  [1] ;  
    ...  
}  

可以看到Parker类实际上用Posix的mutex,condition来实现的。在Parker类里的_counter字段,就是用来记录所谓的“许可”的。
当调用park时,先尝试直接能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回:

void Parker::park(bool isAbsolute, jlong time) {  
  // Ideally we'd do something useful while spinning, such  
  // as calling unpackTime().  
  
  
  // Optional fast-path check:  
  // Return immediately if a permit is available.  
  // We depend on Atomic::xchg() having full barrier semantics  
  // since we are doing a lock-free update to _counter.  
  if (Atomic::xchg(0, &_counter) > 0) return;  

如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:

ThreadBlockInVM tbivm(jt);  
if (_counter > 0)  { // no wait needed  
  _counter = 0;  
  status = pthread_mutex_unlock(_mutex);  

否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:

if (time == 0) {  
  status = pthread_cond_wait (_cond, _mutex) ;  
}  
_counter = 0 ;  
status = pthread_mutex_unlock(_mutex) ;  
assert_status(status == 0, status, "invariant") ;  
OrderAccess::fence();  

当unpark时,则简单多了,直接设置_counter为1,再unlock mutext返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

void Parker::unpark() {  
  int s, status ;  
  status = pthread_mutex_lock(_mutex);  
  assert (status == 0, "invariant") ;  
  s = _counter;  
  _counter = 1;  
  if (s < 1) {  
     if (WorkAroundNPTLTimedWaitHang) {  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0, "invariant") ;  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0, "invariant") ;  
     } else {  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0, "invariant") ;  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0, "invariant") ;  
     }  
  } else {  
    pthread_mutex_unlock(_mutex);  
    assert (status == 0, "invariant") ;  
  }  
}  

简而言之,是用mutex和condition保护了一个_counter的变量,当park时,这个变量置为了0,当unpark时,这个变量置为1。
值得注意的是在park函数里,调用pthread_cond_wait时,并没有用while来判断,所以posix condition里的"Spurious wakeup"一样会传递到上层Java的代码里。

五、应用

LockSupport在Java的工具类应用很广泛,以Java里最常用的类ThreadPoolExecutor为例。先看以下代码:

public class PoolExecutorDemo {
    public static void main(String[] args)throws Exception {
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,5,1000, TimeUnit.SECONDS,queue);

        Future<String> future = poolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5000);
                return "hello";
            }
        });
        String result = future.get();
        System.out.println(result);
    }
}

代码中我们向线程池中扔了一个任务,然后调用Future的get方法,同步阻塞等待线程池的执行结果。
Future#get()是如何阻塞住当前线程,线程池执行完任务后又如何唤醒线程的呢?

从 poolExecutor.submit方法查看源码,进入 AbstractExecutorService#submit 方法,查看线程池submit方法的实现:

public <T> Future<T> submit(Callable<T> task) {
       if (task == null) throw new NullPointerException();
       RunnableFuture<T> ftask = newTaskFor(task);
       execute(ftask);
       return ftask;
   }

在submit方法里,线程池将我们提交的基于Callable实现的任务,封装为基于RunnableFuture实现的任务,然后将任务提交到线程池执行execute(ftask)。
进入newTaskFor方法,看到:return new FutureTask(callable); 就是简单将任务封装成FutureTask。 future.get() 代码中的future本质上就是FutureTask。

接下来看看 FutureTask#get 方法的实现:

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

比较简单,就是判断下当前任务是否执行完毕,如果执行完毕直接返回任务结果,否则进入 awaitDone 方法阻塞等待。

看下 FutureTask#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方法里,首先会用cas操作,将线程封装为WaitNode,保持下来,以供后续唤醒线程时用。再就是调用了LockSupport的park/parkNanos组塞住当前线程。
以上就是线程池阻塞等待任务结果的逻辑,接下来看看线程池执行完任务,唤醒等待线程的逻辑实现。

前边说了,咱们提交的基于Callable实现的任务,已经被封装为FutureTask任务提交给了线程池执行,任务的执行就是 FutureTask#run 方法执行。如下是 FutureTask#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);
    }
}

从 result = c.call(); 看起 ,c.call()就是执行我们提交的任务,boolean ran是判断任务是否执行完。执行完后调用了 set(result) 方法,进入set方法:

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

发现set方法调用了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
}

在这里边,先是通过cas操作将所有等待的线程拿出来,然后便使用 LockSupport#unpark 唤醒每个线程。

六、参考

java并发编程之LockSupport : https://my.oschina.net/LucasZhu/blog/1536002

https://zhuanlan.zhihu.com/p/35159033
https://www.jianshu.com/p/ceb8870ef2c5

【细谈Java并发】谈谈LockSupport:
https://www.jianshu.com/p/1f16b838ccd8

自己动手写把”锁”—LockSupport深入浅出: https://www.cnblogs.com/qingquanzi/p/8228422.html

Java的LockSupport.park()实现分析:
http://hengyunabc.github.io/java-park/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值