详解 java.util.concurrent.Semaphore 信号量

JAVA herman 428浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

前几天群里有网友问我信号量和线程池,我对信号量回答有些错误,后面群里其他网友做了更正,今天由于时间紧张,我就简单的在说一下信号量和线程池。

自从 jdk5.0 开始在 java.util.concurrent 包里提供了 Semaphore 的官方实现,因此大家不需要自己去实现 Semaphore。但是还是很有必要去熟悉如何使用 Semaphore 及其背后的原理。Semaphore(信号量)是一个线程同步结构,用于在线程间传递信号,以避免出现信号丢失,或者像锁一样用于保护一个关键区域。

信号量和线程池的使用场景非常的广泛,最简单的就是 Hystirx 中提供了两种模式执行逻辑:信号量、线程池。所以对于 JUC 这套视频,大家还是很有必要看一看的。

Semaphore 可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在 Windows 下可以设置共享文件的最大客户端访问个数。 

Semaphore 实现的功能就类似厕所有 5 个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当 5 个人中的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造 Semaphore 对象时传入的参数选项。单个信号量的 Semaphore 对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

Semaphore 维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用 Semaphore 可以实现有限大小的链表。另外重入锁 ReentrantLock 也可以实现该功能,但实现上要复杂些。 

Java 信号量 Semaphore

package com.xttblog;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

class Employee implements Runnable {
    private String id;
    private Semaphore semaphore;
    private static Random rand= new Random(47);

    public Employee(String id, Semaphore semaphore) {
        this.id = id;
        this.semaphore = semaphore;
    }

    public void run() {
            try {
                semaphore.acquire();
                System.out.println(this.id + "is using the toilet");
                TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
                semaphore.release();
                System.out.println(this.id + "is leaving");
            } catch (InterruptedException e) {
            }
    }
}

public class ToiletRace {
    // 定义10个人上厕所
    private static final int THREAD_COUNT = 10;
    // 业余草:www.xttblog.com
    private static ExecutorService threadPool = Executors
            .newFixedThreadPool(THREAD_COUNT);
    // 定义5个坑位
    private static Semaphore s = new Semaphore(5);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new Employee(String.valueOf(i), s));
        }

        threadPool.shutdown();
    }
}

这里我定义了 10 个人要上厕所,但是只有 5 个坑位,每个人消耗随机的时间,直接运行上面这段代码,可以看到一开始进去了 5 个人,后来就是陆陆续续的有人进,有人出了。但是正在使用的一定不会超过 5 个的。

acquire()

public void acquire()
    throws InterruptedException

从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。

如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态:某些其他线程调用此信号量的 release()方法,并且当前线程是下一个要被分配许可的线程;或者其他某些线程中断当前线程。

如果当前线程:被此方法将其已中断状态设置为 on ;或者在等待许可时被中断。则抛出 InterruptedException,并且清除当前线程的已中断状态。 

release()

public void release()

释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。
不要求释放许可的线程必须通过调用 acquire() 来获取许可。通过应用程序中的编程约定来建立信号量的正确用法。

另外两个不常用的方法,public int availablePermits()查看现在可用的信号量。public int drainPermits(),这个方法返回即可所有的许可数目,并将许可置 0。public Semaphore(int permits) 其中参数permits就是允许同时运行的线程数目。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » 详解 java.util.concurrent.Semaphore 信号量