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

支持博主:点赞、收藏⭐、留言

目录

    • 1.Semaphore简介
      • 1.1Semaphore是什么
      • 1.2Semaphore的作用
    • 2.Semaphore中的方法(我们在之后的章节中会详细讲解,读者可以先大致看一下)
    • 3.acquire()方法和release()方法
      • 3.1构造方法、acquire()方法和release()方法简介
      • 3.2Semaphore构造器中控制线程并发量为一(某段时间内只能并发一个线程)
      • 3.3Semaphore构造器中控制线程并发量为二(只能并发两个线程,即两个线程交替执行)
      • 3.4release()方法动态添加permits(许可)数量
      • 3.5小结
    • 4.原理
      • 4.1Semaphore的两种策略(公平策略和非公平策略)

1.Semaphore简介


1.1Semaphore是什么

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

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

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

1.2Semaphore的作用

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





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


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

Semaphore类的结构视图:
Java并发包(JUC)中的信号量Semaphore详解,深入浅出Semaphore_第1张图片
官方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.2Semaphore构造器中控制线程并发量为一(某段时间内只能并发一个线程)

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

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();
        }
    }
}

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();
    }
}

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

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

Process finished with exit code 0

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

此时我们只需要把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();
        }
    }
}

运行结果:

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

Process finished with exit code 0

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

3.4release()方法动态添加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();
        }

    }
}

运行结果:

8
5
11

Process finished with exit code 0

3.5小结

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





4.原理

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

Semaphore有两种策略

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

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

具体类图如下:

Java并发包(JUC)中的信号量Semaphore详解,深入浅出Semaphore_第2张图片

简化后的结果如下:

Java并发包(JUC)中的信号量Semaphore详解,深入浅出Semaphore_第3张图片

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

未完待续…


Author:YuShiwen
上次更新日期:2022.05.04

你可能感兴趣的:(#,Java并发与异步编程高级教程,Java,JUC,并发,信号量,Semaphore)