09-Java多线程-6、AQS工具-LockSupport

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/my_momo_csdn/article/details/91617015

一、LockSupport

1.1 初识

  • AQS中依赖的2个类是LockSupport和Unsafe,前者提供线程的(park/unpark操作,后者提供CAS操作),我们
    先看看JDK源码中对LockSupport的描述
用于创建锁和其他同步类的基本线程阻塞原语。

1.2 本质

  • LockSupport的park函数是将当前调用Thread阻塞,而unpark函数则是将指定线程Thread唤醒。
    归根结底,LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码:
    public static void park() {
        UNSAFE.park(false, 0L);
    }
    
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    
    //对应的UNSAFE类中的代码是:
    public native void park(boolean var1, long var2);
    
    public native void unpark(Object var1);
  • 与Object类的wait/notify机制相比,park/unpark有两个优点:
以thread为操作对象更符合阻塞线程的直观定义,操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

1.3 许可

  • 在上面的文字中,我使用了阻塞和唤醒,是为了和wait/notify做对比。其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
  • 关于许可的特点:
1.如果该许可可用,并且可在进程中使用,则调用 #park(...)将会立即返回,否则可能阻塞。
2.如果许可尚不可用,则可以调用#unpark(...) 使其可用。
3.许可不可重入,也就是说只能调用一次park(...) 方法,假如有一个许可,调用park会立即返回,再次调用则会阻塞。
4.许可不可叠加,“许可”是一次性的。比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态,换句话说unpark可以提供许可,但是最多也只能提
供一个许可,调用多次也没用,除非提供一个许可,然后这个许可被使用,然后再提供...。

1.4 示例

  • 如下所示,演示先调用park,线程被阻塞,然后调用unpark,线程可以继续执行。
public class ParkSupportTest {
    /**
    *主线程3秒后才提供许可,因此Thread-1阻塞三秒,获得许可之后再继续执行
    */
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        thread1.start();
        System.out.println("Main execute...");
        SleepTools.second(3);
        LockSupport.unpark(thread1);
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread [" + Thread.currentThread().getName()+"] start..."+new Date());
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName()+"] 1 park end..."+new Date());
        }
    }
}
打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 10:16:22 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 10:16:25 CST 2019
  • 如下所示,演示先调用unpark提供许可,再调用park,线程不会阻塞,说明许可是可以预先提供的,这和线程之间
    的await和notify机制有很大的不同
public class ParkSupportTest1 {
    /**
     * 主线程先提供许可,因此Thread-1 Sleep了1秒,然后立马获得许可之后再继续执行,说明park没有阻塞,就继续执行了
     */
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        thread1.start();
        LockSupport.unpark(thread1);
        System.out.println("Main execute...");
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
            SleepTools.second(1);
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
        }
    }
}

打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:04:45 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:04:46 CST 2019
  • 如下所示,演示先调用unpark多次,线程再多次调用park,第一次线程不会阻塞,但是后面线程会阻塞,
    说明unpark提供的许可是不能叠加的,多次调用也只能提供一个许可。
public class ParkSupportTest2 {
    /**
     * 主线程调用unpark多次,Thread-1 调用park多次,第一次可以获取到许可不会阻塞,后面会阻塞,说明许可不能叠加,
     * 在许可没有被使用掉的情况下,unpark最多只能提供一个许可
     */
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        thread1.start();
        LockSupport.unpark(thread1);
        LockSupport.unpark(thread1);
        LockSupport.unpark(thread1);
        System.out.println("Main execute...");
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
            SleepTools.second(1);
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 2 park end..." + new Date());
        }
    }
}

打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:04:10 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:04:11 CST 2019
  • 如下所示,线程先park阻塞,然后被unpark放行,然后线程再次park,线程会阻塞,说明park不像锁那样获取之后
    可以可以重入,park会用掉许可,如果后面没有提供新的许可,再次park还会阻塞。
public class ParkSupportTest3 {
    /**
     * 主线程调用unpark一次,提供一个许可,Thread-1 调用park多次,第一次可以获取到许可不会阻塞,后面会阻塞,
     * 说明获取许可之后,许可是一次性的
     */
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        thread1.start();
        LockSupport.unpark(thread1);
        System.out.println("Main execute...");
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
            SleepTools.second(1);
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 2 park end..." + new Date());
        }
    }
}

打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:03:45 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:03:46 CST 2019
  • 如下所示,主线程每间隔3秒提供一个许可,子线程则会每三秒获取到一个许可依次往下执行
public class ParkSupportTest4 {
    /**
     * 如下所示,主线程每间隔3秒提供一个许可,子线程则会每三秒获取到一个许可依次往下执行
     */
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        thread1.start();
        System.out.println("Main execute...");
        for (int i = 1; i <= 3; i++) {
            LockSupport.unpark(thread1);
            SleepTools.second(3);
        }
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 2 park end..." + new Date());
            LockSupport.park();
            System.out.println("Thread [" + Thread.currentThread().getName() + "] 3 park end..." + new Date());
        }
    }
}

打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:17:48 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:17:48 CST 2019
Thread [Thread-1] 2 park end...Mon Jun 03 11:17:51 CST 2019
Thread [Thread-1] 3 park end...Mon Jun 03 11:17:54 CST 2019

1.5 小结

  • park和unpark的使用有点类似于一个只能容纳一个信号的通知机制,好比有一个容器,里面只有只能容纳一个信号。
调用park的时候,如果容器里面有信号,线程就会放行并且把信号从容器移除,没有信号的话线程就阻塞,阻塞的
过程中如果容器中有信号了,线程会从容器移除这个信号并往下执行。
调用unpark的时候,逻辑比较简单,如果容器里面没有信号,就放一个进去,如果容器里面已经有了,那也没用,放不进去了。
  • 因为LockSupport的源码是基于UNSAFE来实现的,因此源码可以看的不多,可以阅读最后的参考文章了解其在JDK中的
    部分逻辑,主要还是了解其用法,便于阅读AQS的代码。

参考

猜你喜欢

转载自blog.csdn.net/my_momo_csdn/article/details/91617015