【併發編程】 --- 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章