LockSupport

devtools/2024/10/19 12:51:23/

LockSupport类为构建锁和同步器提供了基本的线程阻塞唤醒原语,JDK中我们熟悉的AQS基础同步类就使用了它来控制线程的阻塞和唤醒,也许我们更加熟悉的阻塞唤醒操作是wait/notify方式,它主要以Object的角度来设计。而LockSupport提供的park/unpark则是以线程的角度来设计,真正解耦了线程之间的同步。

许可机制

线程成功拿到许可则能够往下执行,否则将进入阻塞状态。对于LockSupport使用的许可可看成是一种二元信号,该信号分有许可和无许可两种状态。每个线程都对应一个信号变量,当线程调用park时其实就是去获取许可,如果能成功获取到许可则能够往下执行,否则则阻塞直到成功获取许可为止。而当线程调用unpark时则是释放许可,供线程去获取

java">public class LockSupportTest {private static boolean isLocked = true;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {while (isLocked) {System.out.println(Thread.currentThread().getName() + " is locked");LockSupport.park();}System.out.println(Thread.currentThread().getName() + " finish");}, "thread-1");Thread thread2 = new Thread(() -> {isLocked = false;LockSupport.unpark(thread1);System.out.println(Thread.currentThread().getName() + " unpark thread1");}, "thread-2");thread1.start();TimeUnit.SECONDS.sleep(2);thread2.start();}}// console result
thread-1 is locked
thread-2 unpark thread1
thread-1 finish

需要注意的是在调用LockSupport的park方法时一般会使用while(condition)循环体,如下方框的代码所示,这样能保证在线程被唤醒后再一次判断条件是否符合。

核心方法

该类主要包含两种操作,分别为阻塞操作和唤醒操作

java">public class LockSupport {private LockSupport() {} // Cannot be instantiated.// 设置当前线程等待的对象(下文简称为阻塞对象),该对象主要用于问题排查和系统监控private static void setBlocker(Thread t, Object arg) {UNSAFE.putObject(t, parkBlockerOffset, arg);}// 唤醒一个由 park 方法阻塞的线程。如果该线程未被阻塞,那么下一次调用 park 时将立即返回。这允许“先发制人”式的唤醒机制。public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);}// 阻塞当前线程,如果调用 unpark 方法或线程被中断,则该线程将变得可运行。请注意,park 不会抛出 InterruptedException,因此线程必须单独检查其中断状态。// 入参用来记录导致线程阻塞的对象,方便问题排查。public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);UNSAFE.park(false, 0L);setBlocker(t, null);}// 阻塞当前线程一定的纳秒时间,或直到被 unpark 调用,或线程被中断。// 入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查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);}}// 阻塞当前线程直到某个指定的截止时间(以毫秒为单位),或直到被 unpark 调用,或线程被中断。// 入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查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;   // xorshiftr ^= r >>> 17;r ^= r << 5;}else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)r = 1; // avoid zeroUNSAFE.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); }}
}

LockSupport 中的 blocker

java">    private static final sun.misc.Unsafe UNSAFE;private static final long parkBlockerOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> tk = Thread.class;parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));} catch (Exception ex) { throw new Error(ex); }}private static void setBlocker(Thread t, Object arg) {// Even though volatile, hotspot doesn't need a write barrier here.UNSAFE.putObject(t, parkBlockerOffset, arg);}public
class Thread implements Runnable {/*** The argument supplied to the current call to* java.util.concurrent.locks.LockSupport.park.* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker* Accessed using java.util.concurrent.locks.LockSupport.getBlocker*/volatile Object parkBlocker;}

`tk.getDeclaredField("parkBlocker")`  就是获取线程中的parkBlocker字段

'UNSAFE.objectFieldOffset()' 获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段

` UNSAFE.putObject(t, parkBlockerOffset, arg) ` 该方法有三个参数,第一个参数是操作对象,第二个参数是内存偏移量,第三个参数是实际存储值。该方法理解起来也很简单,就是操作某个对象中某个内存地址下的数据

Dump 线程

"Dump 线程"通常是指获取线程的当前状态和调用堆栈的详细快照。这可以提供关于线程正在执行什么操作以及线程在代码的哪个部分的重要信息。

下面是线程转储中可能包括的一些信息:

  • 线程 ID 和名称:线程的唯一标识符和可读名称。
  • 线程状态:线程的当前状态,例如运行(RUNNABLE)、等待(WAITING)、睡眠(TIMED_WAITING)或阻塞(BLOCKED)。
  • 调用堆栈:线程的调用堆栈跟踪,显示线程从当前执行点回溯到初始调用的完整方法调用序列。
  • 锁信息:如果线程正在等待或持有锁,线程转储通常还包括有关这些锁的信息。

线程转储可以通过各种方式获得,例如使用 Java 的 jstack 工具,或从 Java VisualVM、Java Mission Control 等工具获取。

下面是一个简单的例子,通过 LockSupport 阻塞线程,然后通过 Intellij IDEA 查看 dump 线程信息。

park()

java">public class LockSupportTest {public static void main(String[] args) {LockSupport.park();}private static class User{}
}

运行程序

 

调用 park()方法 dump 线程

java">"main" #1 prio=5 os_prio=0 tid=0x000001b2caaf9800 nid=0x313c waiting on condition [0x0000009afc6ff000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)at LockSupportTest.main(LockSupportTest.java:11)

park(Object blocker)

java">public class LockSupportTest {public static void main(String[] args) {LockSupport.park(new User());}private static class User{}
}

调用 park(Object blocker)方法 dump 线程

java">"main" #1 prio=5 os_prio=0 tid=0x0000024d21c7a000 nid=0x5638 waiting on condition [0x0000006fa98ff000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x000000076b5a9120> (a LockSupportTest$User)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at LockSupportTest.main(LockSupportTest.java:11)

park 和 unpark 的执行顺序

正常来说一般是先park线程阻塞,然后unpark来唤醒该线程。但是如果先执行unpark再执行park,会不会park就永远没办法被唤醒了呢?

在往下分析之前我们先来看看wait/notify方式的执行顺序例子。主线程分别创建了thread1和thread2,然后启动它们。由于thread1会延迟3s再启动,所以先由thead2执行了notify去唤醒阻塞在锁对象的线程,而在三秒后thread1才会执行wait方法,此时它将一直阻塞在那里。所以wait/notify方式的执行顺序会影响到唤醒。

wait 和 notify 的顺序

java">public class LockSupportTest {private static Boolean isLocked = true;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {synchronized (isLocked) {try {System.out.println(Thread.currentThread().getName() + " isLocked.wait()");isLocked.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + " finish");}, "thread-1");Thread thread2 = new Thread(() -> {synchronized (isLocked) {isLocked.notify();System.out.println(Thread.currentThread().getName() + " isLocked.notify()");}}, "thread-2");thread2.start();TimeUnit.SECONDS.sleep(2);thread1.start();}}

控制台输出

park 与 unpark 的顺序

java">public class LockSupportTest {private static Boolean isLocked = true;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + " isLocked=" + isLocked);LockSupport.park();System.out.println(Thread.currentThread().getName() + " finish");}, "thread-1");Thread thread2 = new Thread(() -> {isLocked = false;LockSupport.unpark(thread1);System.out.println(Thread.currentThread().getName() + " unpark thread1");}, "thread-2");thread2.start();//        TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName() + " thread1.start()");thread1.start();}}// console result
main thread1.start()
thread-2 unpark thread1
thread-1 isLocked=false
thread-1 finish

注意:这里不能在 main 线程进行 sleep,因为 thread1 线程尚未启动(start() 没有被调用),那么 LockSupport.unpark 操作不会对这个线程产生影响,因为这个线程还不存在于 JVM 的线程管理中,也就无法对其应用 unpark 操作。

因此,为了确保 LockSupport.unpark 能够正常工作,必须确保线程已经被创建并且已经通过 Thread.start() 方法启动。只有这样,JVM 才能管理这个线程,并允许 LockSupport 类的操作对其产生作用。如果线程还没有启动,你需要先启动线程,然后再根据需要使用 LockSupport.parkLockSupport.unpark 来控制它的阻塞与解除阻塞。

Park 对中断的响应

park方法支持中断,也就是说一个线程调用park方法进入阻塞后,如果该线程被中断则能够解除阻塞立即返回。但需要注意的是,它不会抛出中断异常,所以我们不必去捕获InterruptedException

java">public class LockSupportTest {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " park");LockSupport.park();System.out.println(Thread.currentThread().getName() + " finish");}, "thread-1");thread1.start();TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + " sleep two second later");thread1.interrupt();}
}// console result
thread-1 park
main sleep two second later
thread-1 finish

park 是否释放锁

java">public class LockSupportTest {private static Boolean isLocked = true;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {synchronized (isLocked) {System.out.println(Thread.currentThread().getName() + " LockSupport.park()");LockSupport.park();}System.out.println(Thread.currentThread().getName() + " finish");}, "thread-1");Thread thread2 = new Thread(() -> {synchronized (isLocked) {LockSupport.unpark(thread1);System.out.println(Thread.currentThread().getName() + " LockSupport.unpark(thread1)");}}, "thread-2");thread1.start();TimeUnit.SECONDS.sleep(2);thread2.start();}
}

控制台输出

thread1和thread2两个线程都通过synchronized(lock)来获取锁。由于thread1先启动所以获得锁,而且它调用park方法使得thread1进入阻塞状态,但是thread1却不会释放锁lock。对于thread2这边,因为一直无法获取锁而无法进入同步块,也就没办法执行unpark操作。最终的结果就是造成了死锁,thread1在等thread2的unpark,而thread2却在等thread1释放锁。

总结

LockSupport.park()休眠线程,LockSupport.unpark()唤醒线程,两个方法配合使用。也可以通过LockSupport.parkNanos()指定休眠时间后,自动唤醒。

LockSupport.park()不会释放monitor锁。


线程被打断,LockSupport.park()不会抛出异常,也不会吞噬掉interrupt的状态,调用者可以获取interrupt状态,自行进行判断,线程是由于什么原因被唤醒了。


LockSupport.park()会是线程进入WAITING状态,而LockSupport.parkNanos(long nanos) 会进入TIMED_WAITING状态。

LockSupport.park(Object blocker)和LockSupport.getBlocker(t1)配合使用,可以进行自定义数据传输。

深入理解Java并发线程阻塞唤醒类LockSupport | 二哥的Java进阶之路

并发锁LockSupport原理剖析,四千字多图讲解+多例子+代码分析-腾讯云开发者社区-腾讯云


http://www.ppmy.cn/devtools/127011.html

相关文章

【Unity - 屏幕截图】技术要点

在Unity中想要实现全屏截图或者截取某个对象区域的图片都是可以通过下面的函数进行截取 Texture2D/// <summary>/// <para>Reads the pixels from the current render target (the screen, or a RenderTexture), and writes them to the texture.</para>/…

Scala入门基础(12)抽象类

抽象类&#xff0c;制定标准&#xff0c;不要求去具体实现 包含了抽象方法的类就是抽象类。抽象方法只是有方法名&#xff0c;没有具体方法体的方法 定义抽象类要用abstract&#xff08;抽象&#xff09;关键字 用智能驾驶技术举例&#xff1a;演示&#xff09…

Win11 安装 PostgreSQL 报错解决方案

一、问题概述 在 Win11 系统中安装 PostgreSQL 时&#xff0c;可能会遇到“Problem running post-install”的报错情况。这一报错给用户带来了极大的困扰&#xff0c;使得安装过程无法顺利进行。 二、报错原因分析 &#xff08;一&#xff09;权限不足问题 在 Win11 中&…

sankey.top - 桑基图/桑吉图/流程图/能量流/物料流/能量分析

sankey.top 桑基图大师(SankeyMaster)是您创建复杂桑基图表的首选工具。轻松输入数据并创建桑基图表&#xff0c;准确揭示复杂的数据关系&#xff01; 应用 https://apps.apple.com/cn/app/sankeymaster-sankey-diagram/id6474908221 在线编辑器 https://studio.sankeymaste…

Spring Security 如何进行权限验证

阅读本文之前&#xff0c;请投票支持这款 全新设计的脚手架 &#xff0c;让 Java 再次伟大&#xff01; FilterSecurityInterceptor FilterSecurityInterceptor 是负责权限验证的过滤器。一般来说&#xff0c;权限验证是一系列业务逻辑处理完成以后&#xff0c;最后需要解决的…

python 区间循环数

目录 区间循环加上步长2&#xff1a; 区间循环加上步长1&#xff1a; 方法3&#xff1a; 区间循环加上步长2&#xff1a; for i in range(0, 11, 2):print(i) 区间循环加上步长1&#xff1a; for i in range(start, stop):# 你的代码逻辑pass 方法3&#xff1a; if __nam…

Redis——事务

文章目录 Redis 事务Redis 的事务和 MySQL 事务的区别:事务操作MULTIEXECDISCARDWATCHUNWATCHwatch的实现原理 总结 Redis 事务 什么是事务 Redis 的事务和 MySQL 的事务 概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执行 Redis 的事务和 MySQL 事务的区别:…

Spring Boot 3 声明式接口:能否完全替代 OpenFeign?

Spring Boot 3 声明式接口&#xff1a;能否完全替代 OpenFeign&#xff1f; 在微服务架构中&#xff0c;服务间的通信是一个核心问题。OpenFeign&#xff0c;作为一个声明式的HTTP客户端&#xff0c;极大地简化了服务调用和负载均衡的实现。然而&#xff0c;随着Spring Boot 3…