详解Java信号量-Semaphore

news2025/3/1 3:00:02

第1章:引言

大家好,我是小黑。今天,咱们一起来深入探讨一下Semaphore。在Java中,正确地管理并发是一件既挑战又有趣的事情。当谈到并发控制,大家可能首先想到的是synchronized关键字或者是ReentrantLock。但其实,Java还提供了一个非常强大的工具,就是Semaphore。

Semaphore,直译过来就是“信号量”。在日常生活中,信号灯控制着车辆的通行,防止交通混乱,这其实和Semaphore在程序中的作用颇为相似。Semaphore主要用于控制同时访问特定资源的线程数量,它通过协调各个线程,保证合理的使用公共资源。比方说如果有一家餐馆只允许固定数量的顾客同时用餐,这就是Semaphore的经典应用场景。

第2章:Semaphore的基本概念

让我们先来了解一下Semaphore的基本概念。在Java中,Semaphore是位于java.util.concurrent包下的一个类。它的核心就是维护了一个许可集。简单来说,就是有一定数量的许可,线程需要先获取到许可,才能执行,执行完毕后再释放许可。

那么,这个许可是什么呢?其实,你可以把它想象成是对资源的访问权。比如,有5个许可,就意味着最多允许5个线程同时执行。线程可以通过acquire()方法来获取许可,如果没有可用的许可,该线程就会阻塞,直到有许可可用。

让我们看个简单的例子。假设咱们有一个限制了最多同时3个线程执行的Semaphore:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    // 创建一个Semaphore实例,许可数量为3
    private static final Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        // 创建并启动三个线程
        for (int i = 1; i <= 3; i++) {
            new Thread(new Task(semaphore), "线程" + i).start();
        }
    }

    static class Task implements Runnable {
        private final Semaphore semaphore;

        public Task(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                // 请求许可
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 获取许可,正在执行");
                Thread.sleep(1000); // 模拟任务执行
                System.out.println(Thread.currentThread().getName() + " 执行完毕,释放许可");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                // 释放许可
                semaphore.release();
            }
        }
    }
}

在这个例子中,咱们创建了一个Semaphore实例,设置最大许可数为3。这意味着,最多只能有3个线程同时运行Task中的代码。每个线程在开始执行前,都会尝试通过acquire()方法获取一个许可。

第3章:Semaphore的核心原理

现在,咱们深入一下Semaphore的核心原理。理解这个原理对于掌握Semaphore的高效使用至关重要。在Java中,Semaphore不仅仅是个计数器,它背后的原理和实现逻辑比看起来要复杂得多。

Semaphore的核心是基于AQS(AbstractQueuedSynchronizer)这个框架。AQS是Java并发包中的一个非常重要的组件,它用来构建锁或者其他同步器。简单来说,AQS提供了一种机制,可以让线程在访问某个资源前进入等待状态,并在资源可用时被唤醒。这正是Semaphore的基础。

Semaphore维护了一个许可集,这个集合的大小在初始化时设定。每次调用acquire()方法,Semaphore会试图从这个集合中取出一个许可。如果没有可用的许可,线程就会被阻塞,直到有其他线程释放一个许可。相反,release()方法会增加许可的数量,并有可能唤醒等待的线程。

让小黑通过一段代码来更好地说明这个原理:

import java.util.concurrent.Semaphore;

public class SemaphoreDeepDive {
    public static void main(String[] args) {
        // 初始化一个只有2个许可的Semaphore
        Semaphore semaphore = new Semaphore(2);

        Runnable task = () -> {
            try {
                // 尝试获取许可
                semaphore.acquire();
                System.out.println("线程 " + Thread.currentThread().getName() + " 获取了许可");
                // 模拟任务执行
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                // 释放许可
                semaphore.release();
                System.out.println("线程 " + Thread.currentThread().getName() + " 释放了许可");
            }
        };

        // 创建并启动3个线程
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
    }
}

在这个例子中,Semaphore被初始化为只有两个许可。当三个线程尝试运行时,只有两个能够同时执行。第三个线程必须等待,直到一个许可被释放。这就是Semaphore控制并发的机制。

第4章:使用Semaphore的场景

咱们来聊聊Semaphore在实际编程中的应用场景。理解了Semaphore的基础和原理后,咱们现在可以探索它在实际场景中的具体使用。Semaphore非常灵活,可以用于多种场合,特别是在控制资源访问的并发环境中。

场景一:资源池

想象一下,小黑有一个数据库连接池,这个池子里只有几个数据库连接。如果所有的连接都被占用了,其他需要数据库连接的线程就得等待。这就是Semaphore的经典应用场景。通过限制可用的连接数量,Semaphore确保了不会有太多的线程同时访问数据库。

场景二:限流

在Web服务中,咱们可能想要限制某个服务的并发请求数量,以防止服务器过载。Semaphore可以很容易地实现这个功能。设置一个固定数量的许可,就可以限制同时处理的请求数量。

代码示例

让小黑用代码展示一下这些场景。首先,是一个简单的数据库连接池的示例:

import java.util.concurrent.Semaphore;

public class DatabaseConnectionPool {
    private final Semaphore semaphore;
    private final String[] connectionPool;
    private final boolean[] used;

    public DatabaseConnectionPool(int poolSize) {
        semaphore = new Semaphore(poolSize);
        connectionPool = new String[poolSize];
        used = new boolean[poolSize];
        for (int i = 0; i < poolSize; i++) {
            connectionPool[i] = "连接 " + (i + 1);
        }
    }

    public String getConnection() throws InterruptedException {
        semaphore.acquire();
        return getNextAvailableConnection();
    }

    public void releaseConnection(String connection) {
        if (markAsUnused(connection)) {
            semaphore.release();
        }
    }

    private synchronized String getNextAvailableConnection() {
        for (int i = 0; i < connectionPool.length; i++) {
            if (!used[i]) {
                used[i] = true;
                return connectionPool[i];
            }
        }
        return null; // 不应该发生,semaphore保证了有可用连接
    }

    private synchronized boolean markAsUnused(String connection) {
        for (int i = 0; i < connectionPool.length; i++) {
            if (connection.equals(connectionPool[i])) {
                used[i] = false;
                return true;
            }
        }
        return false;
    }
}

这个代码演示了如何使用Semaphore来控制对有限数量资源(数据库连接)的访问。每个连接在使用前需要获得一个许可,使用完后释放许可。

第5章:Semaphore的高级特性

公平性与非公平性

Semaphore有两种模式:公平模式和非公平模式。公平模式下,线程获得许可的顺序与它们请求许可的顺序一致,就像排队一样。而非公平模式则没有这种保证,线程可以“插队”,这可能会导致某些线程等待时间过长。

在Java中,创建Semaphore时可以指定是公平模式还是非公平模式。默认情况下,Semaphore是非公平的。公平模式通常会有更高的性能开销,因为它需要维护一个更加复杂的内部结构来保证顺序。

可中断操作

在Semaphore中,等待许可的操作可以是可中断的。这意味着如果一个线程在等待一个许可时被中断,它可以选择退出等待。这在处理某些需要响应中断的场景时非常有用。

代码示例

让小黑给你演示一下这两个特性的代码实例:

import java.util.concurrent.Semaphore;

public class SemaphoreAdvancedFeatures {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个公平模式的Semaphore
        Semaphore fairSemaphore = new Semaphore(1, true);

        // 创建并启动两个线程
        Thread t1 = new Thread(new Worker(fairSemaphore), "线程1");
        Thread t2 = new Thread(new Worker(fairSemaphore), "线程2");
        t1.start();
        t2.start();

        // 演示可中断操作
        Thread interruptibleThread = new Thread(() -> {
            try {
                fairSemaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 获取了许可");
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 被中断");
            }
        });
        interruptibleThread.start();
        Thread.sleep(1000); // 等待一会
        interruptibleThread.interrupt(); // 中断线程
    }

    static class Worker implements Runnable {
        private final Semaphore semaphore;

        Worker(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 获取了许可");
                Thread.sleep(2000); // 模拟工作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + " 释放了许可");
            }
        }
    }
}

在这个代码中,小黑创建了一个公平模式的Semaphore,并演示了两个线程按顺序获取许可的情况。同时,还展示了一个线程在尝试获取许可时如何被中断。

第6章:Semaphore的问题与解决方案

问题一:资源耗尽

最常见的问题之一是资源耗尽。当所有许可都被占用,并且持有许可的线程因某种原因无法释放许可时,就会出现资源耗尽的情况。这可能会导致其他线程永久等待,从而造成死锁。

解决方案:确保在使用资源后总是释放许可。可以使用try-finally块来确保即使在发生异常时也能释放许可。

问题二:公平性问题

如前所述,Semaphore可以是公平的或非公平的。在非公平模式下,有可能导致某些线程饥饿,即永远得不到执行的机会。

解决方案:如果需要保证每个线程都有机会执行,可以考虑使用公平模式的Semaphore。

问题三:性能问题

在高并发场景中,Semaphore可能成为性能瓶颈。由于线程频繁地获取和释放许可,可能会导致过多的上下文切换和竞争。

解决方案:适当调整许可的数量,或者寻找其他更适合高并发场景的并发工具。

代码示例

让小黑通过代码来展示如何妥善处理这些问题:

import java.util.concurrent.Semaphore;

public class SemaphoreProblemSolving {
    private static final Semaphore semaphore = new Semaphore(1);

    public static void main(String[] args) {
        Thread thread1 = new Thread(SemaphoreProblemSolving::safeMethod, "线程1");
        Thread thread2 = new Thread(SemaphoreProblemSolving::safeMethod, "线程2");

        thread1.start();
        thread2.start();
    }

    private static void safeMethod() {
        try {
            semaphore.acquire();
            try {
                // 执行关键区域代码
                System.out.println(Thread.currentThread().getName() + " 在执行");
                Thread.sleep(1000);
            } finally {
                semaphore.release(); // 确保总是释放许可
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

在这段代码中,小黑展示了如何使用try-finally块来确保无论如何都会释放Semaphore的许可。这种方式可以减少由于异常导致的资源耗尽问题。

第7章:与其他并发工具的结合使用

结合CountDownLatch

CountDownLatch是一种同步帮助,它允许一个或多个线程等待其他线程完成一系列操作。在某些场景中,咱们可能需要先用Semaphore控制资源访问,然后使用CountDownLatch来同步多个线程的进度。

结合CyclicBarrier

CyclicBarrierCountDownLatch类似,但它允许一组线程相互等待,达到一个共同的障碍点再继续执行。这在需要多个线程在某个点同步执行的场景中非常有用。结合Semaphore,可以在达到共同点之前控制线程对资源的访问。

代码示例

让小黑给咱们展示一个结合使用Semaphore和CountDownLatch的例子:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

public class CombinedSemaphoreCountDownLatch {
    private static final int THREAD_COUNT = 5;
    private static final Semaphore semaphore = new Semaphore(2);
    private static final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Worker(i, semaphore, latch)).start();
        }
        latch.await();  // 等待所有线程完成
        System.out.println("所有线程执行完毕");
    }

    static class Worker implements Runnable {
        private final int workerNumber;
        private final Semaphore semaphore;
        private final CountDownLatch latch;

        Worker(int workerNumber, Semaphore semaphore, CountDownLatch latch) {
            this.workerNumber = workerNumber;
            this.semaphore = semaphore;
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人 " + workerNumber + " 正在工作");
                Thread.sleep(1000); // 模拟工作
                semaphore.release();
                latch.countDown(); // 完成工作,计数减一
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

在这个例子中,小黑创建了一个包含5个线程的场景。使用Semaphore来控制同时工作的线程数量,同时使用CountDownLatch来确保所有线程都完成工作后主线程才继续执行。

第8章:总结

  • 基本概念:Semaphore是一种基于计数的同步工具,用于控制同时访问特定资源的线程数量。
  • 原理理解:Semaphore的实现依赖于AQS(AbstractQueuedSynchronizer),提供了一种机制来管理和控制线程的访问。
  • 实际应用:从资源池管理到限流控制,Semaphore在多种场景中都非常有用。
  • 高级特性:包括公平性和非公平性的选择,以及对线程中断的响应。
  • 问题解决:面对资源耗尽和性能问题,咱们学习了如何妥善处理Semaphore带来的挑战。
  • 与其他工具结合:Semaphore能与CountDownLatch、CyclicBarrier等并发工具结合使用,解决更复杂的并发问题。

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

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

相关文章

Open3D 获取点云坐标最值(17)

Open3D 获取点云坐标最值(17) 一、算法介绍二、算法实现1.代码2.结果人生天地间,忽如远行客 一、算法介绍 快速获取点云块,沿着 x y z 各方向的坐标最值,这些在点云处理中的应用范围是如此广泛,这也是点云最常被用到的关键信息,后续的很多算法都会设置到这一处理方法。…

迈入AI智能时代!ChatGPT国内版免费AI助手工具 peropure·AI正式上线 一个想法写一首歌?这事AI还真能干!

号外&#xff01;前几天推荐的Peropure.Ai迎来升级&#xff0c;现已支持联网模式&#xff0c;回答更新更准&#xff0c;欢迎注册体验&#xff1a; https://sourl.cn/5T74Hu 相信很多人都有过这样的想法&#xff0c;有没有一首歌能表达自己此时此刻的心情&#xff1a; 当你在深…

【LabVIEW FPGA入门】模拟输入和模拟输出

1.简单模拟输入和输出测试 1.打开项目&#xff0c;在FPGA终端下面新建一个VI 2.本示例以模拟输入卡和模拟输出卡同时举例。 3.新建一个VI编写程序&#xff0c;同时将卡1的输出连接到卡2的输入使用物理连线。 4.编译并运行程序&#xff0c;观察是否能从通道中采集和输出信号。 5…

计算机缺失msvcr100.dll如何修复?分享五种实测靠谱的方法

在计算机系统的日常运行与维护过程中&#xff0c;我们可能会遇到一种特定的故障场景&#xff0c;即系统中关键性动态链接库文件msvcr100.dll的丢失。msvcr100.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序来说&#xff…

【动态规划】LeetCode-42. 接雨水

42. 接雨水。 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1…

GPUMD分子动力学模拟-学习与实践

GPUMD分子动力学模拟-学习与实践 【20220813-樊哲勇 |基于GPUMD程序包的机器学习势和分子动力学模拟】 https://www.bilibili.com/video/BV1cd4y1Z7zi?share_sourcecopy_web 纯GPU下的MD分子模型系统软件 https://github.com/brucefan1983/GPUMD 跟GPUMD对接的一些python程…

绘制几何图形(Shape)

目录 1、创建绘制组件 2、形状视口viewport 3、自定义样式 4、场景示例 绘制组件用于在页面绘制图形&#xff0c;Shape组件是绘制组件的父组件&#xff0c;父组件中会描述所有绘制组件均支持的通用属性。具体用法请参考Shape。 1、创建绘制组件 绘制组件可以由以下两种形式…

Servlet-Request

一、预览 在上一篇Servlet体系结构中&#xff0c;我们初步了解了怎么快速本篇将介绍Servlet中请求Request的相关内容&#xff0c;包括Request的体系结构&#xff0c;Request常用API。 二、Request体系结构 我们注意到我们定义的Servlet类若实现Servlet接口时&#xff0c;请求…

第十七周周报

文章目录 摘要目标检测锚框交并比NMS 非极大值抑制输出 文献阅读&#xff1a;SMPL: A Skinned Multi-Person Linear ModelIntroductionRelated WorkModel FormulationTraining评估动态SMPL讨论结论 总结 摘要 本周看了三维人体重建的领域&#xff0c;看了一篇SMPL的文章&#…

C++算法学习心得五.二叉树(4)

1.二叉搜索树中的插入操作&#xff08;701题&#xff09; 题目描述&#xff1a;给定二叉搜索树&#xff08;BST&#xff09;的根节点和要插入树中的值&#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证&#xff0c;新值和原始二叉搜索树中的任意…

Go 知多少?

作为一名已接触过其他语言的开发&#xff0c;再去学习一门新语言可比之前轻松不少&#xff0c; 语言之间存在很多相似点&#xff0c;但是新语言也有自己的不同点&#xff0c;通常我会先了解它与其他语言常遇到的不同点有哪些&#xff0c; 使自己先能够上手编写基础程序&#…

【LabVIEW FPGA入门】LabVIEW FPGA实现I2S解码器

该示例演示了如何使用 LabVIEW FPGA 解码 IS 信号。该代码可用于大多数支持高速数字输入的LabVIEW FPGA 目标&#xff08;例如R 系列、CompactRIO&#xff09;。IS 用于对系统和组件内的数字音频数据进行编码。例如&#xff0c;MP3 播放器或 DVD 播放器内部的数字音频通常使用 …

【b站咸虾米】新课uniapp零基础入门到项目打包(微信小程序/H5/vue/安卓apk)全掌握

课程地址&#xff1a;【新课uniapp零基础入门到项目打包&#xff08;微信小程序/H5/vue/安卓apk&#xff09;全掌握】 https://www.bilibili.com/video/BV1mT411K7nW/?p12&share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 三、vue语法 继续回到官…

飞桨分子动力学模拟-论文复现第六期:复现TorchMD

飞桨分子动力学模拟-论文复现第六期&#xff1a;复现TorchMD Paddle for MD 飞桨分子动力学模拟科学计算 复现论文-TorchMD: A deep learning framework for molecular simulations 本项目可在AIStudio一键运行&#xff1a;飞桨分子动力学模拟PaddleMD-复现TorchMD 【论文复…

浅析三种Anaconda虚拟环境创建方式和第三方包的安装

目录 引言 一、Anaconda虚拟环境创建方式 1. 使用conda命令创建虚拟环境 2. 使用conda-forge创建虚拟环境 3. 使用Miniconda创建虚拟环境 二、第三方包的安装和管理 1. 使用 pip 安装包&#xff1a; 2. 使用 conda 安装包&#xff1a; 三、结论与建议 引言 在当今的数…

Ceph入门到精通-通过 CloudBerry Explorer 管理对象bucket

简介 CloudBerry Explorer 是一款可用于管理对象存储&#xff08;Cloud Object Storage&#xff0c;COS&#xff09;的客户端工具。通过 CloudBerry Explorer 可实现将 COS 挂载在 Windows 等操作系统上&#xff0c;方便用户访问、移动和管理 COS 文件。 支持系统 支持 Wind…

【ceph】在虚拟环境中需要给osd所在的虚拟盘扩容操作

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

力扣每日一练(24-1-13)

如果用列表生成式&#xff0c;可以满足输出的型式&#xff0c;但是不满足题意&#xff1a; nums[:] [i for i in nums if i ! val]return len(nums) 题意要求是&#xff1a; 你需要原地修改数组&#xff0c;并且只使用O(1)的额外空间。这意味着我们不能创建新的列表&#xff…

2024年 13款 Linux 最强视频播放器

Linux视频播放器选择多样&#xff0c;如榛名、MPlayer、VLC等&#xff0c;功能强大、支持多格式&#xff0c;满足各类用户需求 Linux有许多非常强大的播放器&#xff0c;与windows最强视频播放器相比&#xff0c;几乎丝毫不逊色&#xff01; 一、榛名视频播放器 榛名视频播放…

Java副本的概念

在Java中&#xff0c;"副本"&#xff08;copy&#xff09;一词可以用于描述不同的概念&#xff0c;具体取决于上下文。以下是两个常见的用法&#xff1a; 对象的副本&#xff1a;在Java中&#xff0c;当你创建一个对象并将其赋值给另一个变量时&#xff0c;实际上是创…