深入理解Lock Support

第1章:引言

大家好,我是小黑,今天咱们要聊聊Lock Support。Lock Support是Java并发编程的一块基石,它提供了一种非常底层的线程阻塞和唤醒机制,是许多高级同步工具的基础。

为什么要关注Lock Support?线程是并发执行的基本单元。咱们经常会遇到需要控制线程执行顺序的情况,比如防止资源冲突、确保数据一致性。这时候,就需要一些同步机制来拯救局面。Lock Support提供了一种灵活的线程阻塞和唤醒方式,不同于传统的synchronized和ReentrantLock,它更加轻量,更容易融入各种并发场景。

第2章:Lock Support简介

Lock Support,听起来是不是有点像是某个高大上的技术?其实它就是Java.util.concurrent包里的一个工具类。这个类里最重要的就是两个方法:park()unpark()。这两个小伙伴,一个负责让线程停下来,另一个负责让线程继续跑。听起来很简单对吧?但它们可是大有来头。

在Java里,锁和同步是常见的话题。传统的同步工具像synchronizedReentrantLock都是阻塞式的,意味着当一个线程获取不到锁时,就会进入阻塞状态,等待唤醒。这种方式虽然简单,但有时候效率不高,尤其是在高并发场景下。这时,Lock Support就闪亮登场了。

举个例子,假设小黑现在正在写一个并发程序,需要控制线程A和线程B的执行顺序。小黑可以用Lock Support轻松实现:

public class LockSupportExample {public static void main(String[] args) {final Thread threadA = new Thread(() -> {System.out.println("线程A等待信号");LockSupport.park();  // 线程A停下来等待System.out.println("线程A收到信号");});final Thread threadB = new Thread(() -> {System.out.println("线程B发送信号");LockSupport.unpark(threadA);  // 唤醒线程A});threadA.start();threadB.start();}
}

这段代码中,线程A会在调用park()时停下来,直到线程B调用unpark(threadA),线程A才会继续执行。这就是Lock Support的魅力所在,简单而强大。

第3章:基本概念和原理

Lock Support的核心就是两个方法:park()unpark()park() 用来阻塞当前线程,unpark(Thread thread) 则用来唤醒指定的线程。这听起来很像操作系统中的挂起和继续执行的概念,但Lock Support比这更灵活。

park() 并不是传统意义上的锁。它不会去竞争什么资源,只是纯粹地阻塞线程。而且,它还有一个非常酷的特性——不易产生死锁。因为park() 在等待过程中,如果接收到了unpark()的信号,它会立刻返回,这就避免了像synchronized 那样容易陷入死锁的问题。

再来看看unpark()。这个方法的作用是取消对指定线程的阻塞。有趣的是,unpark() 可以在park() 之前调用。这就意味着,如果线程A已经提前被unpark()了,那么当它后续执行park()时,它会感知到这个信号,并且不会真的进入阻塞状态。

import java.util.concurrent.locks.LockSupport;public class ProducerConsumerExample {private static Thread consumerThread;private static Thread producerThread;public static void main(String[] args) {Object data = null;  // 用于存储生产的数据consumerThread = new Thread(() -> {System.out.println("消费者等待数据...");LockSupport.park();  // 消费者线程等待System.out.println("消费者收到数据: " + data);});producerThread = new Thread(() -> {data = produceData();  // 生产数据System.out.println("生产者生产了数据: " + data);LockSupport.unpark(consumerThread);  // 唤醒消费者线程});consumerThread.start();producerThread.start();}private static Object produceData() {// 模拟数据生产过程return "Java数据";}
}

在这段代码里,消费者线程首先启动并调用park(),等待数据。生产者线程生产数据后,调用unpark(consumerThread)来唤醒消费者线程。注意这里的park()unpark() 是如何配合的,它们之间没有明显的锁竞争,却能有效地协调线程间的活动。

Lock Support的工作原理相当于是给线程发放“许可证”。当调用park()时,如果已经有许可证了,它会立刻消费这个许可证并返回;如果没有许可证,线程就会阻塞。当调用unpark()时,就是在给线程发放一个许可证。但有趣的是,这个许可证是不可累积的,无论调用多少次unpark(),每个线程最多只能持有一个许可证。

这种机制的好处是显而易见的。它比起传统的锁操作,更加轻量,更少的锁竞争,也就意味着更高的效率和更低的死锁风险。而且,Lock Support的设计也非常巧妙,它允许unpark()park()之前调用,这给很多并发控制场景提供了更多的灵活性。

使用Lock Support也需要注意一些问题。比如,线程在调用park()后,可能因为中断而返回,但这并不会抛出InterruptedException异常。这就意味着,当线程在等待时被中断,它可能会在没有接收到期望的信号的情况下继续执行。因此,编写依赖于Lock Support的代码时,需要特别留意线程的中断状态。

第4章:Lock Support与线程状态的交互

Lock Support与线程状态

当线程调用LockSupport.park()时,它会进入WAITING状态。在这种状态下,线程是被动的,不会占用任何CPU资源,就好像是在说:“我没事干了,别管我,直到有人叫醒我。” 这种机制对于实现一些等待/通知的并发模式特别有用,因为它减少了资源的消耗。

而当另一个线程调用LockSupport.unpark(目标线程)时,原本在等待的线程会返回到RUNNABLE状态,准备继续执行。这就好比是有人拍拍它说:“嘿,起床时间到了,该干活了。”

代码示例:线程状态变化

让我们通过一个例子来具体看看这是怎么回事。假设小黑想监控一个线程的状态变化。

public class ThreadStateExample {public static void main(String[] args) throws InterruptedException {Thread monitorThread = new Thread(() -> {System.out.println("监控线程运行中...");LockSupport.park();  // 让监控线程进入WAITING状态System.out.println("监控线程被唤醒,继续运行");});monitorThread.start();Thread.sleep(1000);  // 稍微等一会儿System.out.println("监控线程的状态: " + monitorThread.getState()); // 打印监控线程的状态LockSupport.unpark(monitorThread);  // 唤醒监控线程Thread.sleep(100);  // 再等一小会儿System.out.println("监控线程的状态: " + monitorThread.getState()); // 再次打印监控线程的状态}
}

在这个例子中,监控线程开始时处于RUNNABLE状态。当它调用LockSupport.park()后,它就进入了WAITING状态。这时,如果我们打印这个线程的状态,就会看到它是WAITING。然后,当主线程调用LockSupport.unpark(monitorThread)后,监控线程被唤醒,回到了RUNNABLE状态。

这个例子展示了线程如何在不同状态之间转换,尤其是WAITING和RUNNABLE之间的转换。这种转换是非常重要的,因为它让我们可以有效地管理线程,使其在需要的时候等待,不需要的时候又能迅速恢复运行。

第5章:Lock Support在实际应用中的案例分析

案例1:自定义的阻塞队列

首个案例是自定义一个阻塞队列。在并发编程中,阻塞队列是一个常见的数据结构,用于在生产者和消费者之间传递数据。让我们看看如何使用Lock Support来实现一个简单的阻塞队列。

import java.util.concurrent.locks.LockSupport;public class CustomBlockingQueue<T> {private Node<T> head, tail;private int size = 0;private final int capacity;private Thread enqThread, deqThread;public CustomBlockingQueue(int capacity) {this.capacity = capacity;head = tail = new Node<>(null);}public void enqueue(T item) {if (size >= capacity) {enqThread = Thread.currentThread();LockSupport.park(); // 队列满时,阻塞生产者线程}tail = tail.next = new Node<>(item);size++;if (deqThread != null) {LockSupport.unpark(deqThread); // 唤醒消费者线程deqThread = null;}}public T dequeue() {if (size == 0) {deqThread = Thread.currentThread();LockSupport.park(); // 队列空时,阻塞消费者线程}T item = head.next.item;head = head.next;size--;if (enqThread != null) {LockSupport.unpark(enqThread); // 唤醒生产者线程enqThread = null;}return item;}static class Node<T> {T item;Node<T> next;Node(T item) {this.item = item;}}
}

这个阻塞队列中,当生产者发现队列已满时,会调用LockSupport.park()来阻塞自己,直到有空间可用。同理,消费者在队列为空时会被阻塞。这是Lock Support在控制线程状态上的一个典型应用。

案例2:简单的同步锁

下面是一个使用Lock Support实现的简单同步锁。这个锁在设计时考虑到了公平性,即按照线程请求锁的顺序来分配锁。

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;public class FairLock {private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();private final AtomicBoolean locked = new AtomicBoolean(false);public void lock() {Thread current = Thread.currentThread();waiters.add(current);// 只有队列首个元素能获取锁while (waiters.peek() != current || !locked.compareAndSet(false, true)) {LockSupport.park();}waiters.remove();}public void unlock() {locked.set(false);LockSupport.unpark(waiters.peek()); // 唤醒下一个等待线程}
}

在这个锁的实现中,如果当前线程不是队列中的第一个,或者锁已被其他线程占用,它就会调用LockSupport.park()来阻塞自己。当锁被释放时,会唤醒队列中的下一个线程。

第6章:Lock Support与Java并发工具的集成

结合ReentrantLock和Lock Support

让我们先来看一个结合ReentrantLock和Lock Support的例子。假设小黑要实现一个同步机制,在这个机制中,我们想让线程在等待ReentrantLock的锁释放时,能够做一些额外的工作。

import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;public class EnhancedReentrantLock extends ReentrantLock {private Thread leader = null;@Overridepublic void lock() {boolean wasInterrupted = false;while (true) {if (tryAcquire(1)) {if (wasInterrupted) {// 如果之前被中断过,恢复中断状态Thread.currentThread().interrupt();}return;}// 如果已有线程在排队,则阻塞当前线程if (hasQueuedPredecessors() && leader == null) {leader = Thread.currentThread();LockSupport.park(this);leader = null;if (Thread.interrupted()) { // 如果park返回是因为中断wasInterrupted = true;}}}}
}

在这个例子中,小黑扩展了ReentrantLock,添加了一些Lock Support的功能。当有线程在等待锁时,它会通过Lock Support被阻塞。这样做的好处是,可以更灵活地控制线程的等待状态,比如在等待过程中做一些额外的检查或者处理。

结合Semaphore和Lock Support

现在,让我们看一个结合Semaphore(信号量)和Lock Support的例子。信号量是另一种常见的并发控制工具,用于限制对资源的访问。

import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.Semaphore;public class CustomSemaphore {private final Semaphore semaphore;private volatile Thread blocker = null;public CustomSemaphore(int permits) {this.semaphore = new Semaphore(permits);}public void acquire() throws InterruptedException {blocker = Thread.currentThread();semaphore.acquire();blocker = null;}public void release() {semaphore.release();LockSupport.unpark(blocker); // 唤醒阻塞的线程}
}

在这个例子中,小黑创建了一个自定义的信号量,它在内部使用了Semaphore,但是在获取和释放许可的同时,还运用了Lock Support来控制线程的阻塞和唤醒。这样的组合增加了控制的灵活性,可以在更复杂的场景下使用。

第7章:性能考量和最佳实践

Lock Support的性能特性

Lock Support的性能主要体现在它提供的park()unpark()操作上。这两个操作相比于Object.wait()notify()来说,更加轻量级,因为它们不需要进入同步区。这就意味着,Lock Support在高并发环境下,能更好地减少线程的上下文切换,提高系统的整体性能。

但是,这并不意味着Lock Support总是性能最优的选择。例如,在一些特定场景下,使用重量级锁(如ReentrantLock)可能会更有效,尤其是当锁竞争不是非常激烈,或者需要更复杂的锁功能时。

Lock Support的最佳实践

现在,让我们来看几个关于使用Lock Support的最佳实践:

  1. 正确处理中断:当线程在park()时被中断,它会返回,但不会抛出InterruptedException。因此,我们需要检查线程的中断状态,并相应地处理它。

    public void parkAndCheckInterrupt() {LockSupport.park();if (Thread.interrupted()) {// 处理中断逻辑System.out.println("线程被中断了");}
    }
    
  2. 避免虚假唤醒:因为park()可能会无故返回(虚假唤醒),最好在一个循环中调用它,检查某个条件是否满足。

    while (!conditionMet()) {LockSupport.park();
    }
    
  3. 合理使用unpark():由于unpark()可以在park()之前调用,因此我们可以利用这一点来避免不必要的阻塞。

    public void sendData(Object data) {// 先设置数据this.data = data;// 然后唤醒消费者线程LockSupport.unpark(consumerThread);
    }
    
  4. 不要过度依赖Lock Support:虽然Lock Support是一个强大的工具,但并不意味着它总是最佳的解决方案。在选择使用Lock Support之前,应该考虑问题的具体情况,评估是否有更适合的工具或方法。

第8章:总结

  1. 基本概念:Lock Support是一个提供线程阻塞和唤醒功能的工具类,核心方法是park()unpark()。这两个方法提供了一种比传统synchronizedReentrantLock更轻量级的线程同步方式。

  2. 与线程状态的交互park()会使线程进入等待状态,而unpark()则被用来唤醒线程。这种机制使得线程的管理更加灵活,有助于提高并发程序的性能。

  3. 在实际应用中的案例:我们看到了Lock Support在自定义阻塞队列、同步锁等场景的应用,展示了其在复杂并发控制中的实用性。

  4. 与其他并发工具的结合:Lock Support可以与Java中的其他并发工具(如ReentrantLock, Semaphore等)结合使用,为解决复杂的并发问题提供更多可能性。

  5. 性能考量和最佳实践:虽然Lock Support是轻量级的,但在使用时仍需注意其特性,比如正确处理中断、避免虚假唤醒等,以确保并发程序的稳定性和效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/618713.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Databend】行列转化:数据透视和逆透视

文章目录 数据准备数据透视数据逆透视总结 数据准备 学生学科得分等级测试数据如下&#xff1a; drop table if exists fact_suject_data; create table if not exists fact_suject_data (student_id int null comment 编号,subject_level varchar null comment …

Nginx多虚拟主机配置

最近写公司项目&#xff0c;需要和前端小伙伴对接&#xff0c;但是有时候只是后端的一个bug&#xff0c;也不好意思一直让前端小伙伴帮忙起服务&#xff0c;所以想着直接拿测试包在本地起一个前端环境&#xff0c;这样后续开发比较方便&#xff1b;因为公司项目有好几个&#x…

CMake HelloWorld

&#xff08;一&#xff09;CMake使用 CMake使用 1.注释# 这是一个CMakeLists.txt文件cmake_minimum_required(VERSION 3.10)2.add_executable 定义工程会生成一个可执行程序add_executable(可执行程序名 源文件名称)# 样式1:add_executable(app add.c div.c main.c mult.c su…

C语言从入门到实战——数据在内存中的存储方式

数据在内存中的存储方式 前言1. 整数在内存中的存储2. 大小端字节序和字节序判断2.1 什么是大小端2.2 为什么有大小端2.3 练习2.3.1 练习12.3.2 练习22.3.3 练习32.3.4 练习42.3.5 练习52.3.6 练习6 3. 浮点数在内存中的存储3.1 练习3.2 浮点数的存储3.2.1 浮点数存的过程3.2.2…

生态茶园建设方案——福建蜂窝物联

一、项目背景 为了进一步提高茶产业集约化、产业化发展水平&#xff0c;充分运用物联网、互联网等高新技术为产业赋能&#xff0c;加速推动安溪茶产业转型升级&#xff0c;县政府决定在安溪县推进“安溪智慧生态茶园项目”&#xff0c;并以茶叶重镇感德镇实施“安溪智慧生态茶园…

EVA-CLIP: Improved Training Techniques for CLIP at Scale论文解读

文章目录 前言一、摘要二、引言三、贡献四、模型方法五、论文链接总结 前言 最近&#xff0c;我一直在搞多模态大模型相关工作&#xff0c;也深知CLIP结构重要性&#xff0c;而EVA-CLIP论文是在CLIP模型基础上进行了一系列trick&#xff0c;实现优越CLIP模型的方法&#xff0c…

SAP SQVI制作报表及SE93创建事务代码

在平时的项目中&#xff0c;财务想查询所有的凭证明细&#xff0c;SAP的查询凭证FB03不能满足需求&#xff0c;所以用SQVI制作一个简易的查询报表。 1、打开SQVI&#xff0c;填写自开发报表的名称“ZFB03”&#xff0c;点击“创建”&#xff0c;输入自开发报表的名称“凭证明细…

【AIGC】Controlnet:基于扩散模型的文生图的可控性

前言 controlnet可以让stable diffusion的生图变得可控。 文章连接&#xff1a;https://arxiv.org/pdf/2302.05543.pdf 摘要 冻结了stable diffusion的预训练模型并重用它的预训练编码层神经网络结构与零初始化卷积层连接&#xff0c;从零开始逐渐增加参数&#xff0c;并确…

Matlab:toposort

语法&#xff1a; n toposort(G) %调用toposort函数&#xff0c;对有向图G进行拓扑排序&#xff0c;并将排序结果存储在变量n中 n toposort(G,Order,algorithm) [n,H] toposort(___) %使用了两个输出参数的形式来调用toposort函数。除了返回排序结果n外&am…

数字集成电路VLSI复习笔记

逻辑门符号 Inverter CMOS NAND Gate CMOS NOR Gate MOS Capacitor nmos cutoff Linear Saturation Channel Charge Carrier velocity nMOS Linear I-V nMOS Saturation I-V Summary nMOS Operation pMOS Operation Inverter Step Response Delay Definitions 3-input NAND Ca…

只不过孤岛罢了:我的2023年总结

2023已悄然过去&#xff0c;还记得跨年夜那天&#xff0c;我突然接到一星期要期末考的消息&#xff0c;我的内心是多么奔溃&#xff0c;先不说一天一门强度如此之高&#xff0c;重要的是矩阵论&#xff0c;工程优化等等科目&#xff0c;还要速成&#xff0c;于是麻木得预习一日…

怎么理解接口幂等,项目中如何保证的接口幂等

都 2024 年了&#xff0c;竟然还有人不知道接口幂等是什么东西。 hi&#xff0c;大家好&#xff0c;我是 浮生 今天正好有空&#xff0c;给大家分享一下 幂等的实现。 什么是幂等&#xff1f; 一、问题解析 简单来说&#xff0c;就是一个接口&#xff0c;使用相同的参数重复执…

2.右值引用和移动语义

文章目录 右值引用和移动语义&&的特性右值引用优化性能&#xff0c;避免深拷贝移动(move )语义forward 完美转发emplace_back 减少内存拷贝和移动unordered container 无序容器map和unordered_map的差别内部实现机理不同优缺点以及适用处 小结优缺点以及适用处 小结 代…

哈希表的实现(1)----除留余数法实现

一&#xff0c;哈希表的介绍 哈希表是一种通过哈希思想实现的一种数据结构。哈希表这种数据结构的特点便是可以通过一个值快速的定位这个值所在的位置实现插入&#xff0c;删除&#xff0c;查找。在这篇博客里面&#xff0c;我们便来实现一个通过除留余数法实现的一个哈希表。 …

IntersectionObserver

IntersectionObserver 这个API主要实现图片懒加载、加载更多等等。 该API作用是观察两个元素之间有没有交叉&#xff0c;有没有重叠 现在要做的是当图片跟视口有交叉的情况下&#xff0c;把data-src的图片路径替换给src属性 //第一个参数是 回调&#xff0c;第二个参数的 配置…

HarmonyOS4.0 系列——06、渲染之条件渲染、循环渲染以及懒加载渲染

HarmonyOS4.0 系列——06、渲染之条件渲染、循环渲染以及懒加载渲染 if/else&#xff1a;条件渲染 ArkTS 提供了渲染控制的能力。条件渲染可根据应用的不同状态&#xff0c;使用 if、else 和 else if 渲染对应状态下的 UI 内容。 写法和 TS 的一样&#xff0c;简单看一下即可…

【数据结构之树和二叉树】

数据结构学习笔记---007 数据结构之树和二叉树概念篇1、树的概念和结构1.1、树的相关概念1.2、树的存储结构 2、二叉树概念及结构2.1、二叉树概念2.2、满二叉树2.3、完全二叉树2.4、满二叉树或完全二叉树的存储形式 3、堆的概念及结构3.1、堆的性质3.2、堆的意义 4、二叉树的存…

python_selenium_安装基础学习

目录 1.为什么使用selenium 2.安装selenium 2.1Chrome浏览器 2.2驱动 2.3下载selenium 2.4测试连接 3.selenium元素定位 3.1根据id来找到对象 3.2根据标签属性的属性值来获取对象 3.3根据xpath语句来获取对象 3.4根据标签的名字获取对象 3.5使用bs4的语法来获取对象…

解惑:测试圈网红工具 Jmeter 到底难在哪里

作为一名测试人员&#xff0c;你是否也曾经遇到过这些问题&#xff1a; 同样的起点&#xff0c;同样的工作时间&#xff0c;为什么别人接那么多项目&#xff0c;你还是在点点点&#xff1b;为什么别人升职了&#xff0c;而你还在原地踏步&#xff1f; 同样的工作内容&#xf…

数据库的数据类型

文章目录 前言一、数据类型数据类型分类数值类型bit类型小数类型floatdecimal 字符串类型charvarcharchar和varchar比较 日期和时间类型enum和set 前言 一、数据类型 数据类型分类 数值类型 下面我们来创建一个表&#xff0c;表中创建一个tinyint类型的数据。当我们不指定tiny…