【并发编程】 --- Semaphore原理简介 + 使用方法

源码地址:https://github.com/nieandsun/concurrent-study.git
该工具类,在并发编程中主要用来进行并发控制,或者说限流


1 原理简介

Semaphore是信号的意思,但是我觉得将其理解为许可或者说令牌好像更好理解一些,其原理可以用下图进行表示:
在这里插入图片描述
即n个线程都想抢占运行某段代码,但是在它们运行之前必须得先去获取指定数量的许可,比如说:

  • (1)总共的许可就3个,哪个线程想要运行这段代码,必须获取1个许可
  • 假如某个时间线程1、线程2、线程3获取到了许可,则这三个线程就可以运行这段代码了。其他没获取到的,必须等这三个线程中有一个释放了许可,其他线程才能拿到许可,并执行这段代码 —> 即并发数为3。

再比如说:

  • (2)总共的许可还是就3个,哪个线程想要运行这段代码,必须获取3个许可
  • 那么假如某个时间线程1获取到了3个许可(注意:如果是指定必须获取3个许可,那么不会有线程一次只拿到1个或2个的情况,即要么拿到3个,要么1个没拿到),那其他线程就没法获取许可了,这个时候同一时刻内,就只能有一个线程运行该方法了 —》即不存在并发的情况了。

自认为通过上面两个栗子大家肯定知道Semaphore的使用原理了。


2 基本使用方法


2.1 demo1 — 每次获取一个许可,将线程并发数控制为N个

  • code
package com.nrsc.ch2.juctools;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class SemaphoreDemo1 {
    private final static int threadCount = 12;

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    
                    semaphore.acquire(); //获取一个许可

                    //总共3个许可,而每次只需拿到一个许可就可以运行下面的方法
                    //也就是说同一时刻可以允许三个线程拿到许可,即并发数为3
                    test(threadNum); 
                    
                    semaphore.release(); //释放一个许可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int i) throws InterruptedException {
        Thread.sleep(2000);
        log.info("threadNum:{}", i);
    }
}
  • 测试结果

在这里插入图片描述


2.2 demo2 — 每次获取多个许可(或者说所有可获取的许可),使线程并发数变为1

  • code
package com.nrsc.ch2.juctools;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class SemaphoreDemo2 {
    private final static int threadCount = 4;

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {

                    semaphore.acquire(3); //获取3个许可

                    //总共3个许可,每次都全获取了
                    //也就是说同一时刻只允许一个线程拿到许可,即并发数为1
                    test(threadNum);

                    semaphore.release(3); //释放3个许可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int i) throws InterruptedException {
        Thread.sleep(2000);
        log.info("threadNum:{}", i);
    }
}
  • 测试结果:

在这里插入图片描述


3 其他玩法

Semaphore提供的方法还挺多的,如下图所示,但大都大同小异。相信大家看看源码中的注释肯定就知道是干什么的了。 我这里在做两个小demo演示一下tryAcquire的用法,其他用法大家可以自行探索。
在这里插入图片描述
如上图所示,tryAcquire方法有四种使用姿势

  • tryAcquire() — 尝试获取一个许可
  • tryAcquire(long timeout, TimeUnit unit) — 尝试在某个时间段内获取一个许可
  • tryAcquire(int permits) — 尝试获取多个许可
  • tryAcquire(int permits, long timeout, TimeUnit unit) — 尝试在某个时间段内获取多个许可

看到这里我觉得大家应该都直接会用了。。。☺☺☺

需要注意的一点是: 这些方法的返回值都是boolean ,这也就提示我们,当线程竞争特别激烈的时候,我们完全可以让某些线程尝试获取一下执行权,如果实在获取不到,为了更好地保护我们的系统,我们就直接将其舍弃。

这里举两个栗子,简单演示一下其用法。

3.1 demo3 — 尝试获取许可,如果获取不到,直接舍弃 ★★★

package com.nrsc.ch2.juctools;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class SemaphoreDemo3 {
    private final static int threadCount = 520;

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {

                if (semaphore.tryAcquire()) { //尝试获取1个许可
                    //总共3个许可,而每次只需拿到一个许可就可以运行下面的方法
                    //也就是说同一时刻可以允许三个线程拿到许可,即并发数为3
                    test(threadNum);
                    semaphore.release(); //释放3个许可
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int i) {
         try {
            Thread.sleep(1); //这里即使注释掉也是一样的效果
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("threadNum:{}", i);
    }
}

在不看运行结果时,你会不会认为认为能运行test方法的线程数在(3,520)的开区间内,---- 即一开始会有3个线程抢到许可,然后1毫秒之后他们释放掉许可,其他还没开启的线程正好又可以抢到许可???

but,事实并非如此,运行结果如下:
在这里插入图片描述
由这个结果我们可以看到,事实是只要有一个线程没抢到运行的许可,其他线程就也别想再运行了。。。

是不是很神奇!!! —> 有兴趣的看源码研究到底为什么吧,我这里就不深入了。


3.2 demo4 — 尝试一段时间内获取许可,如果获取不到,直接舍弃 ★★★

  • code
package com.nrsc.ch2.juctools;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class SemaphoreDemo4 {
    private final static int threadCount = 520;

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {

                try {
                    //5秒内尝试获取1个许可
                    if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
                        test(threadNum);
                        semaphore.release(); //释放3个许可
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int i) {
        try {
            Thread.sleep(1000); //这里即使注释掉也是一样的效果
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("threadNum:{}", i);
    }
}
  • 运行结果如下:

由此可知,加上时间也是只要有一个线程尝试获取不到许可了,其他线程就也别想再运行了。。。

在这里插入图片描述


end

发布了219 篇原创文章 · 获赞 299 · 访问量 51万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/105152077