Java并发系列之八Semaphore

简介: Java并发系列之八Semaphore

前面我已经讲解过了CountDownLatch和CyclicBarrier。本篇我们来讲解下Semaphore。


Semaphore是指信号量,在计算机的世界里信号量可以使用在数据竞争的场景中。在生活中交通信号灯可以比作现实世界中的Semaphore。Semaphore的作用就是允许或者禁止。比如说红灯禁止通行,绿灯允许通行。计算机世界里的Semaphore会持有多张许可证,举个例子有10张许可证,假设有20个线程同时请求信用量的许可证,那么只能是其中十个线程能够拿到许可证,执行代码。另外10个线程只能等拿到许可证的线程释放许可证。举个生活中的例子。比如在银行办理业务,假设大厅有3个窗口。办理业务的群众有10个。那么每次只能有3个群众能获得叫号的资格(对应计算机拿到许可证),如果有一个群众办理完业务了,那么窗口才能空余出来(对应计算机线程释放许可证),等待的群众才能获得叫号的资格。


我们用代码来模拟下该场景。假设只有3个窗口,有10个群众来办理业务,每个业务办理1~5秒钟不等

public class Bank {
    int[] windows = new int[3];//3个办事窗口
    final boolean[] busy = new boolean[3];//办事窗口是否busy
    String[] persons = new String[10];
    Semaphore semaphore = new Semaphore(3);//最多只有3张许可证
    {
        for (int i = 0; i < 10; i++) {
            persons[i] = "person " + i;
        }
    }
    ExecutorService pool = Executors.newCachedThreadPool();
    public void deal() {
        for (final String person : persons) {
            pool.execute(new Runnable() {
                public void run() {
                    try {
                        semaphore.acquire();
                        int index = 0;
                        synchronized (busy) {
                            for (int i = 0; i < 3; i++) {
                                if (!busy[i]) {//如果该窗口空闲
                                    index = i;
                                    busy[i] = true;
                                    System.out.println("请 " + person + " 到 " + index + " 号窗口办理业务");
                                    break;
                                }
                            }
                        }
                        Random random = new Random();
                        int second = 1 + random.nextInt(5);
                        TimeUnit.SECONDS.sleep(second);
                        System.out.println(person + "在"+index+" 窗口办理完成 费时" + second + "秒");
                        synchronized (busy) {
                            busy[index] = false;
                        }
                    } catch (
                            InterruptedException e
                            )
                    {
                        e.printStackTrace();
                    } finally
                    {
                        semaphore.release();
                    }
                }
            });
        }
    }
    public static void main(String[] args) {
        Bank bank = new Bank();
        bank.deal();
    }
}

输出结果如下

请 person 0 到 0 号窗口办理业务
请 person 1 到 1 号窗口办理业务
请 person 2 到 2 号窗口办理业务
person 1在1 窗口办理完成 费时1秒
请 person 3 到 1 号窗口办理业务
person 0在0 窗口办理完成 费时3秒
请 person 4 到 0 号窗口办理业务
person 2在2 窗口办理完成 费时3秒
请 person 5 到 2 号窗口办理业务
person 3在1 窗口办理完成 费时2秒
请 person 6 到 1 号窗口办理业务
person 4在0 窗口办理完成 费时3秒
请 person 7 到 0 号窗口办理业务
person 5在2 窗口办理完成 费时4秒
请 person 8 到 2 号窗口办理业务
person 6在1 窗口办理完成 费时5秒
请 person 9 到 1 号窗口办理业务
person 8在2 窗口办理完成 费时2秒
person 7在0 窗口办理完成 费时4秒
person 9在1 窗口办理完成 费时3秒

从打印结果我们可以看出,每次最多允许3个人办理业务,每当窗口空闲出来就会叫号。

接下来我们看看Semaphore源码

public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    /** All mechanics via AbstractQueuedSynchronizer subclass */
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {
    }
}

Semaphore持有一个Sync对象,该对象是一个AQS。之前的并发系列我们讲过AQS。它是同步的核心。而Lock类也是持有Sync对象。我们可以把Semaphore看成是一个Lock。区别是Semaphore没有lock和unlock方法,取而代之的是acquire 和release方法


public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
}
public boolean tryAcquire(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.nonfairTryAcquireShared(permits) >= 0;
}
final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

看到share字样,首先这把锁是共享锁。每次获取到了锁,许可证数量-1,如果许可证数量小于0,则获取锁失败,该线程等待

public void release() {
        sync.releaseShared(1);
}
protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

release方法许可证+1,同时通知等待线程,尝试获取许可证


总结下CountDownLatch CyclicBarrier Semaphore的区别


CountDownLatch 适合做汇总相关的工作,比如开启多个线程爬取数据,在所有线程完成任务后,将数据做汇总。


CyclicBarrier适合于竞赛相关的场景,让多个线程在同一时间点执行任务。


Semaphore适用于资源有限的情况下的流量控制。

相关文章
|
2月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
95 4
|
2月前
|
缓存 安全 Java
如何理解Java中的并发?
Java并发指多任务交替执行,提升资源利用率与响应速度。通过线程实现,涉及线程安全、可见性、原子性等问题,需用synchronized、volatile、线程池及并发工具类解决,是高并发系统开发的关键基础。(238字)
245 5
|
5月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
375 83
|
5月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
349 83
|
8月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
7月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
284 0
|
5月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
269 0
|
6月前
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
203 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
8月前
|
Java
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
前言 主播觉得,AQS的原理,就是通过这2个队列的协助,实现核心功能,同步队列(CLH队列)和条件队列(Condition队列)。 同步队列(CLH队列) 作用:管理需要获...
166 18
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码