深入理解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;

    @Override
    public 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.mfbz.cn/a/316770.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

七通道NPN 达林顿管GC2003,专为符合标准 TTL 而制造

GC2003 内部集成了 7 个 NPN 达林顿晶体管&#xff0c;连接的阵列&#xff0c;非常适合逻辑接口电平数字电路&#xff08;例 如 TTL&#xff0c;CMOS 或PMOS 上/NMOS&#xff09;和较高的电流/电压&#xff0c;如电灯电磁阀&#xff0c;继电器&#xff0c;打印机或其他类似的负…

Java项目:05 停车管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 课题意义&#xff1a; 随着时代和科技的进步&#xff0c;人们的生活水平越来越高&#xff0c;私家车的数量不断上涨&#xff0c;随之产生了一些问题&…

力扣:209.长度最小的子数组

1.题目分析&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 …

电子学会2023年12月青少年软件编程(图形化)等级考试试卷(四级)真题,含答案解析

青少年软件编程(图形化)等级考试试卷(四级) 分数:100 题数:24 一、单选题(共10题,共30分) 1. 运行下列程序,输入“abcdef”,程序结束后,变量“字符串”是?( )

PLAN B KRYPTO ASSETS GMBH amp; CO. KG 普兰资产管理公司

引领加密技术不断演进 PLAN B KRYPTO ASSETS普兰资产管理以其独创的「Trident Strategy三叉戟模型」技术为基础&#xff0c;持续推动加密技术的发展&#xff0c;打造 Schutz&#xff08;舒茨盾&#xff09; AI 金融隐私匿名公链。致力于提供高效的技术服务&#xff0c;基于机构…

IC验证——perl脚本ccode_standard——c代码寄存器配置标准化

目录 1 脚本名称 2 脚本路径 3 脚本参数说明 4 脚本操作说明 5 脚本代码 1 脚本名称 ccode_standard 2 脚本路径 /scripts/bin/ccode_standard 3 脚本参数说明 次序 参数名 说明 1 address (./rfdig&#xff1b;.&#xff1b;..&#xff1b;./boot) 指定脚本执行路…

【数字图像处理】素描风格变换(二)—— 代码实现

在绘画风格中,素描风格是一种以黑白灰(或单色)色调为主的绘画风格,注重表现物体的轮廓、结构、明暗关系等细节。在数字图像处理中,素描风格变换是一种图像风格变换的过程,用于呈现素描的视觉效果。本文主要记录使用紫光同创 PGL22G 平台,实现素描风格变换的主要代码。 目…

SwiftUI之深入解析高级布局的实战教程

一、自定义动画 首先实现一个圆形布局的视图容器 WheelLayout&#xff1a; struct ContentView: View {let colors: [Color] [.yellow, .orange, .red, .pink, .purple, .blue, .cyan, .green]var body: some View {WheelLayout(radius: 130.0, rotation: .zero) {ForEach(0.…

如何在使用JetBrains IDE时文本编辑更高效?这个IdeaVim好用

IdeaVim 插件已经发布一段时间了&#xff0c;它帮助开发者利用 Vim 的强大功能扩展 JetBrains IDE。JetBrains 内部有一个专属团队维护此插件&#xff0c;这为您提供了两项优势&#xff1a;以键盘为中心的编辑器和 IDE 的支持。 Vim为文本中的跳转和修改带来了许多灵活性&…

最新靠谱可用的-Mac-环境下-FFmpeg-环境搭建

最近在尝试搭建 FFmpeg 开发环境时遇到一个蛋疼的事&#xff0c;Google 了 N 篇文章竟然没有一篇是可以跑起来的&#xff01; 少部分教程是给出了自我矛盾的配置&#xff08;是的&#xff0c;按照贴出来的代码和配置&#xff0c;他自己都跑不起来&#xff09;&#xff0c;大部…

[Altium Designer] AD PCB相同模块的布局步骤

针对原理图完全相同的模块布局布线很有帮助&#xff1b;一定要对应模块相同操作才具有可行性。 1、原理图中选取一路模块的元器件&#xff0c;快捷键【T→S】即可在对应的PCB中选取对应的元器件&#xff1b;跳转到PCB&#xff0c;快接方式改变右边属性&#xff0c;【ctrla】 …

代币合约 ERC20 Token接口

代币合约 在以太坊上发布代币就要遵守以太坊的规则&#xff0c;那么以太坊有什么规则呢?以太坊的精髓就是利用代码规定如何运作&#xff0c;由于在以太坊上发布智能合约是不能修改和删除的&#xff0c;所以智能合约一旦发布&#xff0c;就意味着永久有效&#xff0c;不可篡改…

【信号与系统】【北京航空航天大学】实验一、信号的MATLAB表示及信号运算

一、实验目的 1、初步掌握 MATLAB 仿真软件的使用&#xff1b; 2、学习使用 MATLAB 产生基本时域信号&#xff0c;并绘制信号波形&#xff1b; 3、学习利用 MATLAB 实现信号的基本运算&#xff1b; 4、利用 MATLAB 分析常用的连续时域信号。 二、实验内容 1、 生成连续信号 …

AI软件开发:探索原理、挑战与未来趋势

AI软件开发已经成为当前最热门和具有前景的技术领域之一。随着人工智能技术的快速发展&#xff0c;AI软件的应用范围也在不断扩大。本文将主要探讨AI软件开发的原理、挑战以及未来的趋势。 首先&#xff0c;AI软件开发的原理是基于机器学习和深度学习算法。机器学习是一种通过…

腾讯云免费云主机有哪些?2024年更新

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云百科txybk.com分享2024年最新腾讯云免费服务器…

GC6153步进电机驱动芯片——低噪声、低振动,应用于摄像机,机器人等产品上

GC6153是双通道5V低压步进电机驱动器具有低噪声、低振动的特点&#xff0c;特别适用于相机的变焦和对焦系统&#xff0c;万向节&#xff0c;摇头机和其他精密&#xff0c;低噪声扫描隧道显微镜控制系统。该芯片为每个通道集成了256微步驱动器通过SPI和I2C接口&#xff0c;用户可…

大语言模型向量数据库

大语言模型&向量数据库 LARGE LANGUAGE MODELSA. Vector Database & LLM WorkflowB. Vector Database for LLMC. Potential Applications for Vector Database on LLMD. Potential Applications for LLM on Vector DatabaseE. Retrieval-Based LLMF. Synergized Exampl…

20240112-确定字符串的两半是否相似

题目要求 给定一个偶数长度的字符串s。把这个字符串分成长度相等的两半&#xff0c;前半部分a&#xff0c;后半部分b。 如果两个字符串的元音字母数目相同&#xff08;a、e、i、o、u、A、E、I、O、U&#xff09;&#xff0c;那么它们就是相同的。区分大小写。 如果a和b相同&…

github新建仓库提交代码(本地命令行)

网页在home页面新建一个仓库之后&#xff0c;复制该仓库的URL&#xff0c;待会要用到在本地打开gitbash 进行初始化并将仓库克隆到本地git init git clone <刚刚复制的仓库URL>进入文件夹&#xff0c;创建文件&#xff0c;可以将要提交的内容写入文档cd <克隆下来的文…

多区域isis配置实验

一、预习&#xff1a; IS-IS&#xff1a;Intermediate System to Intermediate System&#xff0c;中间系统到中间系统&#xff0c;是ISO为它的CLNP&#xff08;ConnectionLess Network Protocol&#xff09;设计的一种动态路由协议&#xff0c;后来为了提供对IP路由的支持&…
最新文章