Java并发包(JUC)中的信号量Semaphore详解,深入浅出Semaphore

举报
YuShiwen 发表于 2022/05/05 23:57:20 2022/05/05
【摘要】 🍋🔥 🍑🚀支持博主:点赞👍、收藏⭐、留言💬 🐾 目录 1.🍅Semaphore简介1.1🍑Semaphore是什么1.2🍑Semaphore的作用 2.🍅Semaph...

🍋🔥 🍑🚀支持博主:点赞👍、收藏⭐、留言💬 🐾

1.🍅Semaphore简介


1.1🍑Semaphore是什么

  • Semaphore也叫信号量,在JDK1.5被引入,位于java.util.concurrent包中,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

  • Semaphore可以看作是synchronized关键字的升级版本,能够控制线程并发的数量,这一点是synchronized关键字做不到的。

  • 内部维护了一个计数器,可加可减,acquire()方法是做减法,release()方法是做加法,具体内容见第三章节。

1.2🍑Semaphore的作用

  • 限制线程并发的数量,如果不限制线程的并发数量,CPU资源会很快被耗尽。





2.🍅Semaphore中的方法(我们在之后的章节中会详细讲解,读者可以先大致看一下)


该部分API我们在之后的章节中会详细讲解,读者可以先大致看一下,之后需要用到此处API的时候可以进行查阅。

Semaphore类的结构视图:
在这里插入图片描述
官方API方法描述具体如下:

  • void acquire()
    从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。

  • void acquire(int permits)
    从该信号量获取给定数量的许可证,阻止直到所有可用,否则线程为 interrupted 。

  • void acquireUninterruptibly()
    从这个信号灯获取许可证,阻止一个可用的。

  • void acquireUninterruptibly(int permits)
    从该信号量获取给定数量的许可证,阻止直到所有可用。

  • int availablePermits()
    返回此信号量中当前可用的许可数。

  • int drainPermits()
    获取并返回所有可立即获得的许可证。

  • protected Collection getQueuedThreads()
    返回一个包含可能正在等待获取的线程的集合。

  • int getQueueLength()
    返回等待获取的线程数的估计。

  • boolean hasQueuedThreads()
    查询任何线程是否等待获取。

  • boolean isFair()
    如果此信号量的公平设置为真,则返回 true 。

  • protected void reducePermits(int reduction)
    缩小可用许可证的数量。

  • void release()
    释放许可证,将其返回到信号量。

  • void release(int permits)
    释放给定数量的许可证,将其返回到信号量。

  • String toString()
    返回一个标识此信号量的字符串及其状态。

  • boolean tryAcquire()
    从这个信号量获得许可证,只有在调用时可以使用该许可证。

  • boolean tryAcquire(int permits)
    从这个信号量获取给定数量的许可证,只有在调用时全部可用。

  • boolean tryAcquire(int permits, long timeout, TimeUnit unit)
    从该信号量获取给定数量的许可证,如果在给定的等待时间内全部可用,并且当前线程尚未 interrupted 。

  • boolean tryAcquire(long timeout, TimeUnit unit)
    如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。






3.🍅acquire()方法和release()方法


3.1🍑构造方法、acquire()方法和release()方法简介

🍋构造方法:

  • new Semaphore(),构造函数的permits参数是许可的意思,代表同一时间内,最多允许多少个线程同时执行acquire()和release()之间的代码

🍋acquire()方法:

  • 无参数的acquire()作用是使用1个permits(许可),是减法操作,即减去对应的permits(许可);
  • 有参数的acquire(),可以指定减去多少个permits(许可)数量。

🍋release()方法:

  • 无参数的release()方法会加上1个permits(许可),是加法操作,即加上对应的permits(许可);
  • 有参数的release()方法,可以动态添加permits(许可),比如new Semaphore(2),之后我们可以通过release(2),把permits(许可)数量变成4,这个可以说明构造方法中中的2并不是最终的permits(许可)数量,而只是初始数量。

3.2🍑Semaphore构造器中控制线程并发量为一(某段时间内只能并发一个线程)

因为我们在构造器中控制了并发的线程数量为一,所以当我们创建三个线程,同时启动,他们之间不会轮流着去执行,只能按照先后顺序执行完了前面一个才能执行后面的线程,具体代码如下:

🍋SemaphoreDemo01类:

import java.util.concurrent.Semaphore;

public class SemaphoreDemo01 {
    // 1.构造函数的permits参数是许可的意思,初始化permits(许可)的数量,代表同一时间内,最多允许多少个线程同时执行acquire()和release()之间的代码,具体看需要用多少个证书
    private Semaphore semaphore = new Semaphore(1);

    public void testMethodDemo() {
        try {
            // 2.无参数的acquire()作用是使用1个permits(许可),是减法操作,即减去对应的permits(许可);
            // 3.有参数的acquire(),可以指定减去多少个permits(许可)数量。
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "开始时间:" + System.currentTimeMillis());
            Thread.sleep(10000);
            System.out.println(Thread.currentThread().getName() + "结束时间:" + System.currentTimeMillis());
            // 4.无参数的release()方法会加上1个permits(许可),是加法操作,即加上对应的permits(许可);
            // 5.有参数的release()方法,可以动态添加permits(许可),比如new Semaphore(2),之后我们可以通过release(2),把permits(许可)数量变成4,这个可以说明构造方法中中的2并不是最终的permits(许可)数量,而只是初始数量。
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

🍋ThreadDemo类如下:

public class ThreadDemo extends Thread {
    private SemaphoreDemo01 semaphoreDemo01;

    public ThreadDemo(SemaphoreDemo01 semaphoreDemo01) {
        super();
        this.semaphoreDemo01 = semaphoreDemo01;
    }

    @Override
    public void run() {
        semaphoreDemo01.testMethodDemo();
    }

    public static void main(String[] args) {
        SemaphoreDemo01 semaphoreDemo01 = new SemaphoreDemo01();
        ThreadDemo a = new ThreadDemo(semaphoreDemo01);
        ThreadDemo b = new ThreadDemo(semaphoreDemo01);
        ThreadDemo c = new ThreadDemo(semaphoreDemo01);
        a.setName("A");
        b.setName("B");
        c.setName("C");
        a.start();
        b.start();
        c.start();
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

🍋运行结果:(线程按照先后顺序执行):

A开始时间:1648477661788
A结束时间:1648477671799
B开始时间:1648477671799
B结束时间:1648477681809
C开始时间:1648477681809
C结束时间:1648477691810

Process finished with exit code 0


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.3🍑Semaphore构造器中控制线程并发量为二(只能并发两个线程,即两个线程交替执行)

🍋此时我们只需要把3.1中SemaphoreDemo01类中的new Semaphore(1);改成new Semaphore(2);。如下:

package com.ysw.concurrent.ConcurrentProgramming.semaphoreAndExchanger;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo01 {
    // 1.构造函数的permits参数是许可的意思,代表同一时间内,最多允许多少个线程同时执行acquire()和release()之间的代码
    private Semaphore semaphore = new Semaphore(2);

    public void testMethodDemo() {
        try {
            // 2.无参数的acquire()作用是使用1个permits(许可),是减法操作,即减去对应的permits(许可);
            // 3.有参数的acquire(),可以指定减去多少个permits(许可)数量。
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "开始时间:" + System.currentTimeMillis());
            Thread.sleep(10000);
            System.out.println(Thread.currentThread().getName() + "结束时间:" + System.currentTimeMillis());
            // 4.无参数的release()方法会加上1个permits(许可),是加法操作,即加上对应的permits(许可);
            // 5.有参数的release()方法,可以动态添加permits(许可),比如new Semaphore(2),之后我们可以通过release(2),把permits(许可)数量变成4,这个可以说明构造方法中中的2并不是最终的permits(许可)数量,而只是初始数量。
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

🍋运行结果:

B开始时间:1648478040379
A开始时间:1648478040379
A结束时间:1648478050379
B结束时间:1648478050379
C开始时间:1648478050379
C结束时间:1648478060387

Process finished with exit code 0


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

🍋ps:当构造方法中传入的permits大于1时,该类并不能保证线程的安全性,因为此时还是可能会出现多个线程共同访问实例变量,导致出现脏数据的情况

3.4🍑release()方法动态添加permits(许可)数量

🍋release()方法可以动态的添加permits(许可)数量,构造方法中的permits(许可)并不是最终的permits(许可)数量,而只是初始数量。代码如下:

import java.util.concurrent.Semaphore;

public class ReleaseMethodDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(8);
        try {
            System.out.println(semaphore.availablePermits());
            semaphore.acquire();
            semaphore.acquire(2);
            System.out.println(semaphore.availablePermits());
            semaphore.release();
            semaphore.release(2);
            semaphore.release(3);
            System.out.println(semaphore.availablePermits());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

🍋运行结果:

8
5
11

Process finished with exit code 0

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

3.5🍑小结

  • Semaphore内部维护了一组虚拟的许可(permits),许可的数量可以通过构造函数的参数指定。
  • 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
    访问资源后,使用release释放许可。





4.🍅原理

4.1🍑Semaphore的两种策略(公平策略和非公平策略)

Semaphore有两种策略

在上面的3.1中我们提到过Semaphore的构造方法

  • 如果是一个参数的构造方法new Semaphore(int permits),默认采用的是非公平策略,对应的NonfairSync类;
  • 如果想使用公平策略,那就需要使用两个参数的构造方法new Semaphore(int permits,boolean fair),当第二个参数为true时,才会采用公平策略,对应的FairSync类。

具体类图如下:

在这里插入图片描述

简化后的结果如下:

在这里插入图片描述

由类图可以看出,Semaphore中有属性Sync,而Sync类是继承于AQS的,Sync只是对 AQS的一个简单包装,起到了一个修饰作用,我们可以认为Semaphore还是使用AQS实现的。

未完待续…


Author:YuShiwen
上次更新日期:2022.05.04

文章来源: blog.csdn.net,作者:MrYuShiwen,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/MrYushiwen/article/details/124578964

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。