当前位置: 首页 > news >正文

Day44 | J.U.C中的LockSupport详解

在之前的文章中,我们已经熟悉了Java并发包中的原子类。今天,我们将继续深入J.U.C的核心,探讨一个看着不太起眼但实际却很重要的底层工具——LockSupport。

LockSupport是java.util.concurrent包里线程阻塞和唤醒机制的基础。

如果我们把ReentrantLock、Semaphore等高级同步工具看成精密仪器,那LockSupport就是制造这些仪器所依赖的最核心的零件之一。

理解他,便于理解我们之后将要学习的AQS (AbstractQueuedSynchronizer)。

一、为什么需要LockSupport?

在LockSupport出现之前,我们有两种主要的方式来让线程等待和唤醒:

1、使用Thread.suspend()和Thread.resume()

这对方法已经被废弃。因为太容易导致死锁。如果一个持有锁的线程被suspend(),他就永远不会释放锁,导致其他等待这个锁的线程无限期地阻塞。

2、使用Object.wait()和Object.notify()/notifyAll()

这是最经典和常用的方式。但是这种方式也有一定程度的限制。

比如必须在synchronized块里调用,wait()和notify()必须跟对象的监视器锁绑定,这就意味着线程必须先获取锁,才能进行等待或通知操作,不够灵活。

还会存在信号丢失的问题,比如一个线程先于另一个线程调用notify(),之后另一个线程才调用wait(),那么这个通知信号就丢失了,wait()的线程会无限等待下去。

二、LockSupport的核心机制

LockSupport给每个线程都关联了一个许可(Permit)。这个许可像一张通行证,他是一个二进制的信号量(要么有,要么没有)。

LockSupport最核心的两个方法就是围绕这个许可工作的:

park方法

public static void park() {U.park(false, 0L);}

如果当前线程持有许可,那么他会消耗掉这个许可,并立即返回,线程继续运行。

如果当前线程没有许可,那么他就会阻塞,直到有其他线程给他发放许可。

unpark方法

public static void unpark(Thread thread) {if (thread != null)U.unpark(thread);}

给一个指定的线程发放一个许可。

如果thread当前正因为park()而阻塞,他会被立刻唤醒。

如果thread当前没有阻塞,那么这次发放的许可就会被攒起来(不是累加,最多一个)。当这个线程下一次调用park()的时候,他会立刻消耗掉这个许可,从而避免阻塞。

unpark()可以先于park()调用,并且许可不会丢失。这就解决了wait/notify的信号丢失问题。

当我们打开LockSupport的源码,其实会发现代码量并不多:

 

其中重要的就是

添加图片注释,不超过 140 字(可选)

Unsafe被称之为Java的上帝之手,是因为他可以执行内存操作等一些底层操作。

parkBlocker是Thread类的一个字段,用来调试和监控,记录当前线程是被哪个对象所阻塞的。LockSupport.park(Object blocker)就会设置这个字段。

UNSAFE.park()和UNSAFE.unpark()这两个native方法,实现都在JVM内部(C/C++编写),最终会调用操作系统的线程调度原语,实现真正意义上的线程挂起和恢复。

三、LockSupport实战

概念和术语通常都会让人摸不着头脑,惯例还是直接上手写一下,接下来我们看一下两个LockSupport的代码示例:

1、park与unpark方法的使用

package com.lazy.snail.day44;import java.util.concurrent.locks.LockSupport;/*** @ClassName LockSupportDemo1* @Description TODO* @Author lazysnail* @Date 2025/9/1 13:56* @Version 1.0*/
public class LockSupportDemo1 {public static void main(String[] args) throws InterruptedException {Thread worker = new Thread(() -> {System.out.println(Thread.currentThread().getName() + ": 即将调用park()进入等待...");// 这里线程还没有许可,线程会阻塞LockSupport.park();System.out.println(Thread.currentThread().getName() + ": 已被unpark,继续执行。");}, "工人线程");worker.start();System.out.println("主线程休眠2秒,准备为工人线程发放许可...");Thread.sleep(2000);System.out.println("主线程调用unpark() ...");// 给worker线程发放一个许可LockSupport.unpark(worker);}
}

示例代码中主线程创建并启动了worker线程。

worker打印输出后,执行LockSupport.park()。由于没有许可,worker阻塞,线程状态变成WAITING。

主线程sleep(2000),保证worker已经真的阻塞在park()。

然后主线程调用LockSupport.unpark(worker),给worker发放了一个许可,如果线程正阻塞在park(),会被唤醒并消费掉许可(许可回到0)。

worker从park()返回,打印输出后线程结束。

这段代码演示的就是LockSupport最核心的一次性许可机制,park()消费许可进入/退出等待,unpark(thread)给指定线程发放最多1个许可。

2、unpark先于park执行

之前我们说wait/notify会有信号丢失的问题,通过下面的案例来看看LockSupport存不存在这个问题。

package com.lazy.snail.day44;import java.util.concurrent.locks.LockSupport;/*** @ClassName LockSupportDemo2* @Description TODO* @Author lazysnail* @Date 2025/9/1 14:43* @Version 1.0*/
public class LockSupportDemo2 {public static void main(String[] args) throws InterruptedException {Thread worker = new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + ":等待2秒...");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":即将调用park()...");LockSupport.park();System.out.println(Thread.currentThread().getName() + ":park()执行完毕,没有阻塞。");}, "工人线程");worker.start();System.out.println("主线程提前给工人线程发放许可...");LockSupport.unpark(worker);}
}

示例代码中主线程启动worker,然后马上unpark(worker),许可被置成了1。

worker线程sleep(2s)醒来后调用park(),发现许可是1,马上返回并消费。

worker线程继续执行并结束,全程不阻塞。

这个示例说明了先unpark,后park也不会阻塞。

3、park()对线程中断的响应

当一个被park()阻塞的线程被interrupt()时,park()方法会立即返回,并且该线程的中断状态会被设置为true。

package com.lazy.snail.day44;import java.util.concurrent.locks.LockSupport;/*** @ClassName LockSupportDemo3* @Description TODO* @Author lazysnail* @Date 2025/9/1 15:59* @Version 1.0*/
public class LockSupportDemo3 {public static void main(String[] args) throws InterruptedException {Thread worker = new Thread(() -> {System.out.println("工人线程:即将park...");LockSupport.park();System.out.println("工人线程:已被唤醒。");System.out.println("工人线程中断状态:" + Thread.currentThread().isInterrupted());});worker.start();Thread.sleep(2000);System.out.println("主线程:中断工人线程...");worker.interrupt();}
}

示例代码中,worker线程被阻塞,然后在主线程中被中断,park方法就马上返回了,中断状态变成了true。

四、核心属性及方法

核心属性

private static final Unsafe U = Unsafe.getUnsafe();private static final long PARKBLOCKER= U.objectFieldOffset(Thread.class, "parkBlocker");private static final long TID= U.objectFieldOffset(Thread.class, "tid");

U:Unsafe类的一个实例,LockSupport使用他来调用底层的原生park和unpark函数。

PARKBLOCKER:一个long类型的值,他保存了Thread对象内部parkBlocker字段的内存偏移量。parkBlocker是一个对象,可以跟一个线程关联,来表示他被阻塞的原因。

TID:也是一个long类型的值,存储了Thread对象内部tid(线程ID)字段的内存偏移量。这样LockSupport就可以直接从内存里拿到线程的ID。

核心方法

public static void unpark(Thread thread) {if (thread != null)U.unpark(thread);}public static void park() {U.park(false, 0L);}public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);U.park(false, 0L);setBlocker(t, null);}

unpark(Thread thread)、park()和park(Object blocker)已经在第三节中做了介绍。

parkNanos(long nanos)方法是带超时的park(),表示最多阻塞nanos纳秒。

parkUntil(long deadline)方法是带截止日期的park(),表示最多阻塞到deadline这个绝对时间点。

getBlocker(Thread t)用来获取指定线程t的blocker对象,主要用于监控和诊断。

五、LockSupport和AQS的关系

LockSupport实际是AQS框架的线程调度引擎。

AQS内部维护了一个等待线程的队列。

当一个线程尝试获取锁失败后,AQS会把他包装成一个节点(Node)加入等待队列,然后调用 LockSupport.park(this)把这个线程安全地挂起。

当持有锁的线程释放锁时,AQS会从队列中找到需要被唤醒的后继节点,然后调用 LockSupport.unpark(node.thread) 来精确地唤醒那个等待的线程。

结语

通过阅读本文,以及前期讲的Thread.sleep(ms)、Object.wait()、Condition.await(),我把这些线程阻塞的方法列一个表,方便理解对别:

特性 / 方法

Thread.sleep(ms)

Object.wait()

Condition.await()

LockSupport.park()

是否释放锁

不释放

释放synchronized锁

释放Lock锁

不释放

调用要求

无要求

必须在synchronized块中

必须持有Lock

无要求

唤醒方式

时间到期

notify/notifyAll

signal/signalAll

unpark/interrupt

响应中断

抛出InterruptedException

抛出InterruptedException

抛出InterruptedException

不抛异常,直接返回并设置中断状态

信号丢失问题

不适用

会丢失 (先notify后wait)

会丢失 (先signal后await)

不会丢失 (先unpark后park)

底层实现

JVM/OS

对象监视器(monitor)

AQS + LockSupport.park

Unsafe + OS线程调度原语

下一篇预告

Day45 | J.U.C中AQS的完全指南(上)

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

http://www.dtcms.com/a/511758.html

相关文章:

  • 网络安全生态及学习路线
  • 深度学习-卷积神经网络基础
  • 广州教育网站设计公司在建工程项目查询
  • 【瀑布流大全】分析原理及实现方式(微信小程序和网页都适用)
  • wordpress网站的常规安全设置经验分享
  • 代码随想录Day53|110. 字符串接龙、105.有向图的完全联通、106. 岛屿的周长
  • 做婚恋网站这几年做哪个网站致富
  • 【案例实战】听歌学英语鸿蒙APP从零到上架全流程回顾
  • 基于频域的数字盲水印blind-watermark
  • 三、网站开发使用软件环境中小企业建站的方法
  • 开源 Linux 服务器与中间件(八)数据库--MariaDB
  • Mac OS 安装 VirtualBox
  • wordpress卡密系统源码主题站长工具查询seo
  • 宁波快速建站公司附近的装修公司地点
  • 物联网运维中的自适应网络拓扑重构技术
  • jenkins介绍与部署
  • Attention:MHA->MQA->GQA->MLA
  • 拥塞控制原理
  • Flink Kafka 生产者原理与实现
  • 路由器和机顶盒的射频核心:深入解析PA、LNA、PHY与滤波器
  • Java----set
  • python编程网站推荐郑州云帆网站设计
  • 如何做论文网站给我一个用c 做的网站
  • 青岛网站排名公司自己的网站如何让百度收录
  • MQTT主题架构的艺术:从字符串拼接走向设计模式
  • i.MAX6ULL Linux LED 字符设备驱动代码分析
  • Linux中基数树的初始化
  • 4.3 二维数组
  • 【C语言实战(40)】C语言查找算法:从基础到实战的效率进阶
  • 洛谷 P2949 [USACO09OPEN] Work Scheduling G