4、LockSupport 2022-02-15 19:30 > 以下基于JDK1.8 ### LockSupport介绍 LockSupport主要功能就是阻塞唤醒,和wait()、notify()实现的效果差不多。 主要方法是:`LockSupport.park();` 阻塞当前执行的线程,`LockSupport.unpark(Thread);`唤醒某个线程。 接下来写个小代码看下效果: ```java package com.example.demo.core.LockSupport; import java.util.concurrent.locks.LockSupport; /** * @author: HanXu * on 2022/2/9 * Class description: LockSupport 简单使用 */ public class Demo1 { public static void main(String[] args) { LockSupport.park(); System.out.println("我输出了吗"); } } ``` 执行之后代码一直阻塞,没有任何内容输出,可以知道代码阻塞在了`LockSupport.park();`处。 ```java package com.example.demo.core.LockSupport; import java.util.concurrent.locks.LockSupport; /** * @author: HanXu * on 2022/2/9 * Class description: LockSupport 简单使用 */ public class Demo1 { public static void main(String[] args) { Thread thread1 = new Thread(() -> { System.out.println("thread1开始执行"); LockSupport.park(); System.out.println("thread1输出了吗"); }); thread1.start(); //主线程唤醒thread1 LockSupport.unpark(thread1); } } ``` 执行结果: ``` thread1开始执行 thread1输出了吗 ``` 可以看到主线程唤醒了thread1。 #### LockSupport源码 LockSupport的源码很简短,我们来看一下: ```java package java.util.concurrent.locks; import sun.misc.Unsafe; public class LockSupport { private LockSupport() {} // Cannot be instantiated. private static void setBlocker(Thread t, Object arg) { UNSAFE.putObject(t, parkBlockerOffset, arg); } public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); } public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } 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); } } public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(true, deadline); setBlocker(t, null); } public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); return UNSAFE.getObjectVolatile(t, parkBlockerOffset); } public static void park() { UNSAFE.park(false, 0L); } public static void parkNanos(long nanos) { if (nanos > 0) UNSAFE.park(false, nanos); } public static void parkUntil(long deadline) { UNSAFE.park(true, deadline); } static final int nextSecondarySeed() { int r; Thread t = Thread.currentThread(); if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) { r ^= r << 13; r ^= r >>> 17; r ^= r << 5; } else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0) r = 1; UNSAFE.putInt(t, SECONDARY, r); return r; } 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); } } } ``` #### 方法分类 大致来说,我们可用的方法分为以下4类(或3类): ```java //阻塞,可指定时间,相对时间和绝对时间 void park() void parkNanos(long nanos) void parkUntil(long deadline) //阻塞,在前面的基础上添加了blocker阻塞信息,可以传递给外部信息。 void park(Object blocker) void parkNanos(Object blocker, long nanos) void parkUntil(Object blocker, long deadline) //解除某个线程的阻塞 void unpark(Thread thread) //获取某个线程的阻塞信息 Object getBlocker(Thread t) ``` #### 各方法练习 ##### 第一组:指定时间阻塞 ```java package com.example.demo.core.LockSupport; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; /** * @author: HanXu * on 2022/2/9 * Class description: LockSupport 简单使用 */ public class Demo1 { public static void main(String[] args) { //阻塞2秒后自动唤醒,纳秒为单位 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2)); System.out.println("是否自动唤醒1?"); //绝对时间,这里也设置为当前时间之后的2秒,入参单位是毫秒 LockSupport.parkUntil(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(2)); System.out.println("是否自动唤醒2?"); } } /* 是否自动唤醒1? 是否自动唤醒2? */ ``` ##### 第二组:添加阻塞信息的阻塞 ```java Thread thread1 = new Thread(() -> { System.out.println("thread1开始执行"); LockSupport.park("这里是阻塞信息"); }); thread1.start(); //main线程延迟执行,确保thread1先执行 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } Object blocker = LockSupport.getBlocker(thread1); System.out.println("main线程获取到的阻塞信息:" + blocker.toString()); LockSupport.unpark(thread1); ``` 上述代码中,thread1先执行,然后阻塞,阻塞时添加了阻塞信息;然后main线程获取阻塞信息,并输出。这里的阻塞信息是Object,任意对象都可以,为方便演示,我这里使用了字符串。 需要注意的是,阻塞信息只能在阻塞期间获取,一旦解除了阻塞,就获取不到了。 ```java Thread thread1 = new Thread(() -> { System.out.println("thread1开始执行"); LockSupport.park("这里是阻塞信息"); }); thread1.start(); //main线程延迟执行,确保thread1先执行 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } //先唤醒thread1 LockSupport.unpark(thread1); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } Object blocker = LockSupport.getBlocker(thread1); System.out.println("main线程获取到的阻塞信息:" + blocker.toString()); ``` 执行结果: ``` thread1开始执行 Exception in thread "main" java.lang.NullPointerException at com.example.demo.core.LockSupport.Demo1.main(Demo1.java:71) ``` 可以看到阻塞解除后,就获取不到阻塞信息了。 ##### 先unpark再park 上面我们都是先park()阻塞后再唤醒,那如果先unpark()再park()呢?不妨来试一下: 先unpark(主线程),在尝试park()自身,看看是否阻塞住 ```java Thread mainThread = Thread.currentThread(); LockSupport.unpark(mainThread); LockSupport.park(); System.out.println("我能执行就代表没有被阻塞住"); ``` 输出:我能执行就代表没有被阻塞住,说明没有阻塞。 咦,这是为什么呢?我们知道wait() notifyAll() 、 signalAll() await()都不能实现先唤醒,再阻塞,为什么LockSupport可以呢? #### 原理:permit(许可证) 回答上述问题之前,我们不如先看一下源码,看看park()、unpark()是怎么实现的。 park(): ```java `LockSupport` public static void park() { UNSAFE.park(false, 0L); } `Unsafe` public native void park(boolean var1, long var2); ``` 好吧,park()是直接使用了底层的native方法。 unpark(): ```java `LockSupport` public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); } `Unsafe` public native void unpark(Object var1); ``` 好吧,unpark()也是。 只能查阅资料了... 找出大部分说法是这样的: LockSupport 的设计思路是为每一个线程设置一个 permit,其实就是一个值,类似于 AQS 中的 state。但 permit 没有显示的存在于 LockSupport 的源码中,而 state 却显示的存在于 AQS 的源码中( private volatile int state; )。 permit 默认值(初始值)是 0,最小值也是 0,最大值是 1。0 表示许可证不可用,1 表示许可证可用。 执行park()时,会判断当前permit 值,如果是1,则不阻塞;如果是0,则阻塞。不论是几,完成前面的动作后,都将permit 设为0。 阻塞过程中:如果有设超时时间,则会阻塞到超时时间;如果permit 的值变为1,则会停止阻塞。 无论有没有阻塞,执行unpark(),都会将permit 设置为1。 > (若 permit 值为 0,则执行 park() 方法时就会阻塞当前线程,直至超时或有可用的 permit;若 permit 为 1 ,则执行 park() 方法时会将 permit 值设置成 0,不会阻塞当前线程。 > > 不管 permit 的值是 0 还是 1,unpark 方法都会将 permit 设置成 1,也就说多次 unpark (中间没有 park)后,permit 的值仍是 1。) 通过上面的表述我们大概可以知道,unpark()会先将许可证设置为1,且多次执行unpark()(中间不执行park())的效果不会叠加,和只执行一次unpark()一样。我们可以代码验证一下: ```java Thread mainThread = Thread.currentThread(); LockSupport.unpark(mainThread); LockSupport.unpark(mainThread); LockSupport.unpark(mainThread); LockSupport.park(); System.out.println("我第一次park()通过了"); LockSupport.park(); System.out.println("我第二次park()也通过了"); ``` 可以看到,输出“我第一次park()通过了”后,程序就卡住了,这也证明了上面的表述。 #### 中断特性 还有一个点,就是park()会被中断唤醒。即`thread.interrupt()`也会唤醒park()住的thread线程,用此方法唤醒的线程,下次执行到park(),就不会阻塞了。相当于给线程添加了中断标记,线程知道自己需要被中断处理,所以即使后面还有park(),也不会生效了。 下面代码中,thread1先执行,内部经历了两次park();而main线程没有unpark(thread1),而是使用`thread1.interrupt()`将其中断: ```java Thread thread1 = new Thread(() -> { LockSupport.park(); System.out.println("thread1第一次解除阻塞"); LockSupport.park(); System.out.println("thread1第二次解除阻塞"); }); thread1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); ``` 执行结果: ``` thread1第一次解除阻塞 thread1第二次解除阻塞 ``` 果然,interrupt()后,线程不再受到park()影响。 注意:使用`thread.interrupt()`后,线程被添加了中断标记,如果使用`Thread.interrupted();`,将线程的中断标记去掉,那么下次执行pakr()时,就仍会阻塞了(和正常情况一样)。 #### 总结 1、park() 分三类,每类分两种,官方推荐用带 blocker 参数的那一种 park()、park(Object blocker) parkNanos(long nanos)、parkNanos(Object blocker, long nanos) parkUntil(long deadline)、parkUntil(Object blocker, long deadline) 2、park() 与 unpark() 之间没有严格的调用先后顺序 permit = 1 表示可用,permit = 0 表示不可用;permit 属于线程私有 park() 消耗 permit,将 permit 从 1 设置成 0;unpark() 则将 permit 设置成 1,不管设置前的值是 1 还是 0 permit 可用,则 park 不会阻塞当前线程,将 permit 设置成 0,线程继续往下执行,否则 park 会阻塞当前线程 unpark() 会设置指定线程的 permit = 1,并唤醒指定的线程 ### 题目 1、用两个线程,一个输出数字,一个输出字母,交替输出 1A2B3C4D...26Z > 题目来源:https://www.cnblogs.com/youzhibing/p/14399143.html 一个线程循环输出1,2,3,4,5... 另一个线程循环输出A,B,C,D,E... 要达到交替输出的效果,则需要线程间通信,即线程间协调合作:线程A输出1后通知另一个线程,让他工作,输出A;然后线程B工作后通知另一个线程,让他输出2... 。一个线程工作时另一个线程需要等待。 ```java package com.example.demo.core.Lock.LockSupport; import java.util.concurrent.locks.LockSupport; /** * @author: HanXu * on 2022/1/14 * Class description: 用两个线程,一个输出数字,一个输出字母,交替输出 1A2B3C4D...26Z */ public class Demo2 { public static void main(String[] args) { ThreadA threadA = new ThreadA(); ThreadB threadB = new ThreadB(); threadA.setAnotherThread(threadB); threadB.setAnotherThread(threadA); threadA.start(); threadB.start(); //1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z } } class ThreadA extends Thread { private Thread anotherThread; public void setAnotherThread(Thread anotherThread) { this.anotherThread = anotherThread; } @Override public void run() { for (int i = 1; i < 27; i++) { //因为是输出1A2B3C4D,所以1先输出,这里直接输出,零一个线程进来就需要阻塞 System.out.print(i); //输出后唤醒另一个线程,然后阻塞自己 LockSupport.unpark(anotherThread); LockSupport.park(); } } } class ThreadB extends Thread { private Thread anotherThread; public void setAnotherThread(Thread anotherThread) { this.anotherThread = anotherThread; } @Override public void run() { for (int i = 'A'; i < 'Z' + 1; i++) { LockSupport.park(); System.out.print((char) i); //输出后唤醒另一个线程 LockSupport.unpark(anotherThread); } } } ``` ### tips 最后注意:这里说的LockSupport的阻塞,其实指的是等待,是让线程进入WAITING状态(跟wait()、await()一样,是等待),而不是BLOCKED状态。(很好理解,这里没有涉及到锁的抢占。) 状态验证: ```java Thread thread1 = new Thread(() -> { System.out.println("thread1开始运行时的状态:" + Thread.currentThread().getState()); LockSupport.park(); }); System.out.println("thread1刚创建的状态:" + thread1.getState()); thread1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread1在park()后的状态:" + thread1.getState()); ``` 执行结果: ``` thread1刚创建的状态:NEW thread1开始运行时的状态:RUNNABLE thread1在park()后的状态:WAITING ``` --END--
发表评论