LockSupport最全使用方法 原理介绍

原理

    官方说是线程阻塞的基本原语,可以用来创建锁或者其它的同步类(ReentrantLock基于该类实现)。每个线程使用LockSupport都会关联一个 “许可证”。

    某个线程调用LockSupport.park,如果对应的“许可证”可用,则此次调用立即返回,否则线程将会阻塞直到中断发生、超时或者许可证状态变为可用。LockSupport.unpark(thread)可以使线程的许可证可用,但是unpark不能累积,只能使许可证从不可用到可用,不能累加可用次数。也就是说连续调用unpark和调用一次unpark效果是一样的。

public class LockSupport {
     /**
     * 给某个线程颁发许可证,唤醒线程
     */
    public static void unpark(Thread thread);
     /**
     * 阻塞调用线程直到线程中断或者许可证状态变为可用,如果许可证可用(之前调用过一次unpark)则立 
     * 即返回
     * blocker是设置阻塞线程的对象,追踪问题用吧
     */    
    public static void park(Object blocker);
     /**
     * 阻塞调用线程直到线程中断、等待nanos纳秒或许可证状态变为可用,如果许可证可用(之前调用过一 
     *次unpark)则立即返回
     * blocker是设置阻塞线程的对象,追踪问题用吧
     */
    public static void parkNanos(Object blocker, long nanos);
     /**
     * 阻塞调用线程直到线程中断、到来截止时间(毫秒)或者许可证状态变为可用,如果许可证可用(之前调            
     * 用过一次unpark)则立即返回
     * blocker是设置阻塞线程的对象,追踪问题用吧
     */
    public static void parkUntil(Object blocker, long deadline);
     /**
     * 阻塞调用线程直到线程中断或者许可证状态变为可用,如果许可证可用(之前调用过一次unpark)则立 
     * 即返回
     */
    public static void park();
     /**
     * 阻塞调用线程直到线程中断、等待nanos纳秒或许可证状态变为可用,如果许可证可用(之前调用过一 
     *次unpark)则立即返回
     */
    public static void parkNanos(long nanos);
     /**
     * 阻塞调用线程直到线程中断、到来截止时间(毫秒)或者许可证状态变为可用,如果许可证可用(之前调            
     * 用过一次unpark)则立即返回
     */
    public static void parkUntil(long deadline);
}

     park底层调用的是Unsafe类的park方法


public final class Unsafe {
    /**
    * isAbsolute表示绝对时间时间还是相对时间
    * time 时间,isAbsolute为true就是毫秒时间戳,绝对时间;isAbsolute为false就是纳秒相对时间,多    
    * 少纳秒以后醒来
    **/
    public native void park(boolean isAbsolute, long time);
}

    LockSupport的效果跟Thread.sleep类似,但是它跟线程sleep有以下不同:

  1. sleep和park都阻塞当前线程的执行,两者都不释放占有的锁资源
  2. sleep需要指定时间,park可指定也可不指定;
  3. sleep不能被其它线程唤醒,park可以被其它线程唤醒;
  4. sleep需要捕获InterruptedException异常,park不需要;
  5. park如果想知道唤醒的原因是否是InterruptedException,需要去判断线程的中断状态Thread.interrupted()。

 

应用举例

    jdk的很多同步操作是基于LockSupport做的,例如FutureTask的get操作(阻塞获取Future的结果),当任务状态没有完成时线程调用LockSupport.park,并将作为FutrueTask对象的等待节点放在waiters链表里面。源码如下所示(关键部分笔者添加了注释):

public class FutureTask<V> implements RunnableFuture<V> {
    ......
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        // 
        //笔者注:jdk很多源码都使用这种循环的风格,每次循环只执行一个操作。例如创建一个等待节点入 
        //waiters链,分两次循环,第一次创建节点,第二次循环入waiters链。笔者认为这样做的好处是:
        //该任务可能在“创建节点”和“入waiters链”中间完成,这样第二次循环就不是入waiters链而是直 
        //接返回。其它情况也是一样,任务的完成是异步的,随时可能在某个语句之后完成。这样写能让线 
        //程尽快返回。
        //
        for (;;) {
            //
            //笔者注:唤醒线程的原因如果是中断,则要抛中断异常 并且将当前线程从waiters链中移除
            //
            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)
            // 
            //笔者注:如果等待节点没有入队,则入waiters链
            //
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                //
                //笔者注:如果设置了等待时间,且等待时间未到,则parkNanos阻塞线程一定的时间
                //
                LockSupport.parkNanos(this, nanos);
            }
            else
                //
                //笔者注:阻塞线程
                //
                LockSupport.park(this);
        }
    }
    ......
}

   当任务完成,执行线程会遍历waiters链表,并唤起每个waiter对应的线程。源码如下所示(关键部分笔者添加了注释):

public class FutureTask<V> implements RunnableFuture<V> {
    ......
    private void finishCompletion() {
        // assert state > COMPLETING;
        //
        //笔者注:利用自旋抢到执行权,然后去遍历waiters链表
        //
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                //
                //笔者注:遍历waiters链表,唤醒对应等待该任务的线程
                //
                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
    }
    ......
}

Demo

LockSuport各方法场景实验,demo代码如下:

public class LockSupportDemo {
    private static Thread threadA1 = new Thread() {
        public void  run() {
            System.out.println("A1: before park");
            LockSupport.park();
            System.out.println("A1: after unpark ");
        }
    };


    private static Thread threadA2 = new Thread() {
        public void  run() {
            //睡一小会 保证A1先执行
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("A2 interrupted");
            }
            System.out.println("A2: before unpark A1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("A2 interrupted");
            }
            System.out.println("A2: 2s later");
        }
    };


    /**
     *  A1 call park, then main call unpark(A1)
     *  期望:
     *     A1: before park
     *     A2: before unpark A1
     *     A2: 2s later
     *     A1: after park
     */
    protected static void testPark1() {
        threadA1.start();
        threadA2.start();
        try {
            Thread.currentThread().sleep(5000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }
        LockSupport.unpark(threadA1);
    }

    private static Thread threadB1 = new Thread() {
        public void  run() {
            //睡一小会 保证main可以先执行unpark
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("B1 interrupted");
            }
            System.out.println("B1: before park");
            LockSupport.park();
            //LockSupport.park();
            System.out.println("B1: after unpark ");
        }
    };


    private static Thread threadB2 = new Thread() {
        public void  run() {
            //比B1多睡50ms 保证B1先执行
            try {
                Thread.currentThread().sleep(100);
            }catch (InterruptedException e) {
                System.out.println("B2 interrupted");
            }
            System.out.println("B2: before unpark B1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("B2 interrupted");
            }
            System.out.println("B2: 2s later");
        }
    };
    /**
     *  main call unpark(B1), then B1 call park
     *  期望:
     *  B1: before park
     *  B1: after unpark
     *  B2: before unpark B1
     *  B2: 2s later
     */
    protected static void testPark2() {
        threadB1.start();
        //C睡50ms 保证unpark先调用
        LockSupport.unpark(threadB1);
        //LockSupport.unpark(threadC);
        threadB2.start();
    }


    private static Thread threadC1 = new Thread() {
        public void  run() {
            System.out.println("C1: before park");
            LockSupport.parkNanos(3000000000l);
            System.out.println("C1: after unpark 3s later");
        }
    };


    private static Thread threadC2 = new Thread() {
        public void  run() {
            //比C1多睡50ms 保证C1先执行
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("C2 interrupted");
            }
            System.out.println("C2: before unpark C1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("C2 interrupted");
            }
            System.out.println("C2: 2s later");
        }
    };

    /**
     * C1 call parkNanos(3000000), then main call unpark(C1) 5s later
     *  期望:
     *  C1: before park
     *  C2: before unpark C1
     *  C2: 2s later
     *  C1: after unpark 3s later
     *  main: 5s later
     */
    protected static void testParkNanos() {
        threadC1.start();
        threadC2.start();
        try {
            Thread.currentThread().sleep(5000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }
        System.out.println("main: 5s later");
        LockSupport.unpark(threadC1);
    }

    private static Thread threadD1 = new Thread() {
        public void  run() {
            System.out.println("D1: before park");
            //3s后
            Long deadLine = System.currentTimeMillis() + 3000;
            //过去的一个时间 则立即park不阻塞
            //Long deadLine = System.currentTimeMillis() - 1000;
            LockSupport.parkUntil(deadLine);
            System.out.println("D1: after unpark 3s later");
        }
    };


    private static Thread threadD2 = new Thread() {
        public void  run() {
            //比D1多睡50ms 保证D1先执行
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("D2 interrupted");
            }
            System.out.println("D2: before unpark D1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("D2 interrupted");
            }
            System.out.println("D2: 2s later");
        }
    };

    /**
     * D1 call parkUtil(currentTime + 3000), then main call unpark(D1) 5s later
     *  期望:
     *  D1: before park
     *  D2: before unpark D1
     *  D2: 2s later
     *  D1: after unpark 3s later
     *  main: 5s later
     */
    protected static void testParkUtil() {
        threadD1.start();
        threadD2.start();
        try {
            Thread.currentThread().sleep(5000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }
        System.out.println("main: 5s later");
        LockSupport.unpark(threadD1);
    }

    public static void main(String[] args) {
        System.out.println("**************");
        LockSupportDemo.testPark1();

        //睡6s保证 下面test不影响前面的test输出
        try {
            Thread.currentThread().sleep(6000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }

        System.out.println("**************");
        LockSupportDemo.testPark2();

        //睡6s保证 下面test不影响前面的test输出
        try {
            Thread.currentThread().sleep(6000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }

        System.out.println("**************");
        LockSupportDemo.testParkNanos();

        //睡6s保证 下面test不影响前面的test输出
        try {
            Thread.currentThread().sleep(6000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }

        System.out.println("**************");
        LockSupportDemo.testParkUtil();
    }
}

运行结果:

**************
A1: before park
A2: before unpark A1
A2: 2s later
A1: after unpark 
**************
B1: before park
B1: after unpark 
B2: before unpark B1
B2: 2s later
**************
C1: before park
C2: before unpark C1
C2: 2s later
C1: after unpark 3s later
main: 5s later
**************
D1: before park
D2: before unpark D1
D2: 2s later
D1: after unpark 3s later
main: 5s later

总结

总结一下LockSupport的使用要点:

  1. 线程调用park阻塞自己(类似sleep),直到中断、超时或者其它线程调用unpark;
  2. 线程调用park之前,如果其它线程调用了unpark,则会立即返回;
  3. 线程唤醒以后,线程并不知道是什么原因被唤醒,需要去判断时间、线程状态(Thread.interrupted());
  4. 线程start之前,unpark没有用,线程start之后park仍然会阻塞;
  5. 线程调用park阻塞不会释放线程占用的资源(锁);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章