Table of Contents

LockSupport工具类

JUC提供了LockSupport工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础,是一个提供锁机制的工具类。

简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。

LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是使用Unsafe类实现的,下面主要介绍LockSupport的几个主要函数。

LockSupport主要方法

方法名称 描述
void park() 如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()会马上返回,否则调用线程会被禁止参与线程调度,也就是会被阻塞挂起。在三种情况下,当前线程会获得许可继续运行:1. 其他某个线程将当前线程作为目标调用 unpark。2. 当前线程被其他某个线程中断interrupt()方法。3. 该调用不合逻辑的返回。
void park(Object blocker) Thread类中存在volatile Object parkBlocker字段,用来存放park方法传递的blocker对象,相比于无参的park()方法,额外设置了该字段。
void parkNanos(long nanos) 与park方法类似,不同在于此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。
void parkNanos(Object blocker, long nanos) 相比于park(Object blocker)方法多了一个超时时间。
void parkUntil(Object blocker, long nanos) 阻塞当前线程知道deadline时间(从1970年开始deadline时间的毫秒数)
void unpark(Thread thread) 此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。

示例说明

  1. 使用wait/notify实现同步

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    public class WaitAndNotifyDemo {
    
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();
            synchronized (myThread) {
                try {
                    myThread.start();
                    // 主线程睡眠3s
                    Thread.sleep(3000);
                    System.out.println("before wait");
                    // 阻塞主线程
                    myThread.wait();
                    System.out.println("after wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class MyThread extends Thread {
    
        public void run() {
            synchronized (this) {
                System.out.println("before notify");
                notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("after notify");
            }
        }
    }
    

    结果

    1
    2
    3
    4
    
    before wait
    before notify
    after notify
    after wait
    

    代码具体的流程图如下

    img

    使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。具体代码如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    class MyThread extends Thread {
        public void run() {
            synchronized (this) {
                System.out.println("before notify");            
                notify();
                System.out.println("after notify");    
            }
        }
    }
    
    public class WaitAndNotifyDemo {
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();        
            myThread.start();
            // 主线程睡眠3s
            Thread.sleep(3000);
            synchronized (myThread) {
                try {        
                    System.out.println("before wait");
                    // 阻塞主线程
                    myThread.wait();
                    System.out.println("after wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }            
            }        
        }
    }
    
    1
    2
    3
    
    before notify
    after notify
    before wait
    

    说明: 由于先调用了notify,再调用的wait,此时主线程还是会一直阻塞。

  2. 使用park/unpark实现线程同步

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    
    import java.util.concurrent.locks.LockSupport;
    
    public class ParkAndUnparkDemo {
    
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread(Thread.currentThread());
            myThread.start();
            //Thread.sleep(2000);
            System.out.println("before park");
            // 获取许可
            LockSupport.park("ParkAndUnparkDemo");
            System.out.println("after park");
        }
    
    
        static class MyThread extends Thread {
            private Object object;
    
            public MyThread(Object object) {
                this.object = object;
            }
    
            public void run() {
                System.out.println("before unpark");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取blocker
                System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
                // 释放许可
                LockSupport.unpark((Thread) object);
                // 休眠500ms,保证先执行park中的setBlocker(t, null);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 再次获取blocker
                System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
    
                System.out.println("after unpark");
            }
        }
    }
    

    运行结果

    1
    2
    3
    4
    5
    6
    
    before park
    before unpark
    Blocker info ParkAndUnparkDemo
    after park
    Blocker info null
    after unpark
    

    先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,并且第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker)函数中的setBlocker(t, null)函数,所以第二次调用getBlocker时为null。

    上例是先调用park,然后调用unpark,现在修改程序,先调用unpark,然后调用park。在主线程中设置等待时间,使得先调用unpark,得到运行结果。

    1
    2
    3
    4
    5
    6
    
    before unpark
    Blocker info null
    Blocker info null
    after unpark
    before park
    after park
    

    可以看到,在先调用unpark,再调用park时,仍能够正确实现同步,不会造成由wait/notify调用顺序不当所引起的阻塞。因此park/unpark相比wait/notify更加的灵活。

    更多关于park、unpark、sleep、wait、await的区别参考这里