併發工具類(三)控制併發執行緒的數量 Semphore

王小胖醬發表於2019-01-22

前言

  JDK中為了處理執行緒之間的同步問題,除了提供鎖機制之外,還提供了幾個非常有用的併發工具類:CountDownLatch、CyclicBarrier、Semphore、Exchanger、Phaser;
  CountDownLatch、CyclicBarrier、Semphore、Phaser 這四個工具類提供一種併發流程的控制手段;而Exchanger工具類則提供了線上程之間交換資料的一種手段。

簡介

  

Semaphore(訊號量)是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。
很多年以來,我都覺得從字面上很難理解Semaphore所表達的含義,只能把它比作是控制流量的紅綠燈,比如XX馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入XX馬路,但是如果前一百輛中有五輛車已經離開了XX馬路,那麼後面就允許有5輛車駛入馬路,這個例子裡說的車就是執行緒,駛入馬路就表示執行緒在執行,離開馬路就表示執行緒執行完成,看見紅燈就表示執行緒被阻塞,不能執行。

應用場景

  Semaphore可以用於做

流量控制
,特別公用資源有限的應用場景,比如資料庫連線。假如有一個需求,要讀取幾萬個檔案的資料,因為都是IO密集型任務,我們可以啟動幾十個執行緒併發的讀取,但是如果讀到記憶體後,還需要儲存到資料庫中,而資料庫的連線數只有10個,這時我們必須控制只有十個執行緒同時獲取資料庫連線儲存資料,否則會報錯無法獲取資料庫連線。這個時候,我們就可以使用Semaphore來做流控,程式碼如下:

public class SemaphoreTest {

    private static final int THREAD_COUNT = 30;

    private static ExecutorService threadPool = Executors
            .newFixedThreadPool(THREAD_COUNT);

    private static Semaphore s = new Semaphore(10);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        s.acquire();
                        System.out.println("save data");
                        s.release();
                    } catch (InterruptedException e) {
                    }
                }
            });
        }

        threadPool.shutdown();
    }
}
複製程式碼

在程式碼中,雖然有30個執行緒在執行,但是隻允許10個併發的執行。Semaphore的構造方法Semaphore(int permits) 接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示允許10個執行緒獲取許可證,也就是最大併發數是10。Semaphore的用法也很簡單,首先執行緒使用Semaphore的acquire()獲取一個許可證,使用完之後呼叫release()歸還許可證。還可以用tryAcquire()方法嘗試獲取許可證。

Semphore的方法摘要

1、獲取許可

  API中提供了多種的方式獲取鎖:

  • 可以獲取一個、多個許可;
  • 提供阻塞、非阻塞、超時的方式獲取許可;
  • 除了可中斷、還提供一個非中斷的方式獲取鎖;

public void acquire() throws InterruptedException
從此訊號量獲取一個許可,在提供一個許可前一直將執行緒阻塞,否則執行緒被

中斷。


public void acquire(int permits) throws InterruptedException

獲取多個許可。

從此訊號量獲取給定數目的許可,在提供這些許可前一直將執行緒阻塞,或者執行緒已被

中斷。


public void acquireUninterruptibly()
從此訊號量中獲取許可,在有可用的許可前將其阻塞。

不可中斷。


public void acquireUninterruptibly(int permits)

獲取多個許可。

從此訊號量獲取給定數目的許可,在提供這些許可前一直將執行緒阻塞。

不可中斷。


public boolean tryAcquire()
僅在呼叫時此訊號量存在一個可用許可,才從訊號量獲取許可。

非阻塞的方式嘗試獲取許可。


public boolean tryAcquire(int permits)
僅在呼叫時此訊號量中有給定數目的許可時,才從此訊號量中獲取這些許可。
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
如果在給定的等待時間內此訊號量有可用的所有許可,並且當前執行緒未被中斷,則從此訊號量獲取給定數目的許可。

超時等待獲取許可


public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException
如果在給定的等待時間內,此訊號量有可用的許可並且當前執行緒未被中斷,則從此訊號量獲取一個許可。

2、許可的釋放

public void release( ):
釋放一個許可,將其返回給訊號量。
public void release(int permits)
釋放給定數目的許可,將其返回到訊號量。

3、提供的監控方法

public int availablePermits( )
返回此訊號量中當前可用的許可數
public int drainPermits()
獲取並返回立即可用的所有許可
public final int getQueueLength()
返回正在等待獲取的執行緒的估計數目。該值僅是估計的數字,因為在此方法遍歷內部資料結構的同時,執行緒的數目可能動態地變化。此方法用於監視系統狀態,不用於同步控制。
public final boolean hasQueuedThreads()
查詢是否有執行緒正在等待獲取。
public boolean isFair()
如果此訊號量的公平設定為 true,則返回 true。

protected 方法:

protected Collection
返回一個 collection,包含可能等待獲取的執行緒。因為在構造此結果的同時實際的執行緒 set 可能動態地變化,所以返回的 collection 僅是盡力的估計值。所返回 collection 中的元素沒有特定的順序。
protected void reducePermits(int reduction)
根據指定的縮減量減小可用許可的數目。此方法在使用訊號量來跟蹤那些變為不可用資源的子類中很有用

@ Example 獲取、釋放多個許可

try {
    Semaphore semaphore = new Semaphore(5);
    //獲取一個許可
    semaphore.acquire();
    //一次性獲取4個許可
    semaphore.acquire(4);
    System.out.println("Semaphore 剩下的許可數量:"+semaphore.availablePermits());
    //一次性釋放5個許可
    semaphore.release(5);
    System.out.println("Semaphore 剩下的許可數量:"+semaphore.availablePermits());
    //再釋放5個許可
    semaphore.release();
    semaphore.release();
    semaphore.release(3);
    System.out.println("Semaphore 剩下的許可數量:"+semaphore.availablePermits());
   
    } catch (InterruptedException e) {
    e.printStackTrace();
複製程式碼

執行結果:

Semaphore 剩下的許可數量:0
Semaphore 剩下的許可數量:5
Semaphore 剩下的許可數量:10
複製程式碼

 從上面的執行結果可以看出,

構造方法的 new Semaphore(5)中引數5並不是最終的許可數量,可以通過release()方法增加許可數量。

本人測試用例

package com.wxx.demo;

import com.wxx.demo.util.IdUtiles;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

@RunWith(SpringRunner.class)
//@SpringBootTest(classes = LeisureWebApplication.class)
public class TaskTest {

    @Test
    public void taskTest(){

        Runnable task = new Runnable() {

            int count = 0;

            @Override
            public void run() {

                count ++;
                try{
                    String id = IdUtiles.creatId();
                    System.out.println(id);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(count);
                System.out.println("Thread : " + Thread.currentThread().getId());

                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        };

        double executeTime = this.executeTime(100, task);
        System.out.println("執行時間: " + executeTime);
    }

    private double executeTime(int taskCount,Runnable task){

        CountDownLatch start = new CountDownLatch(1);
        CountDownLatch end = new CountDownLatch(taskCount);

        for (int i = 0; i < taskCount ; i++) {
            Thread thread = new Thread() {

                public void run(){
                    try {
                        start.await();

                        try {
                            task.run();
                        }finally {
                            end.countDown();
                        }

                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };

            thread.start();
        }

        long startTime = System.nanoTime();

        //開啟開關
        start.countDown();

        long endTime = System.nanoTime();

        return endTime - startTime;
    }
}
複製程式碼

package com.wxx.demo.util;

import java.text.SimpleDateFormat;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @Author : leisure
 * @Date : 2019/1/17
 */
public class IdUtiles {

    private static String lead = "leisure";
    private static int Guid = 100;
    private static Semaphore semaphore = new Semaphore(5,false);
    /**
     * 建立以字串打頭結尾自增的唯一id
     * @return
     */
    public static synchronized String creatId() throws InterruptedException{
        //測試控制方法內的併發執行緒數 測試放開synchronized
        //semaphore.acquire();
        semaphore.tryAcquire(1,1000, TimeUnit.MILLISECONDS);
        int i = semaphore.availablePermits();
        System.out.println("當前可用許可" + i);


        int i1 = semaphore.drainPermits();
        System.out.println("當前立即可用的許可" + i1);

        boolean b = semaphore.hasQueuedThreads();
        System.out.println("當前是否有執行緒等待" + b);

        boolean fair = semaphore.isFair();
        System.out.println("當前訊號是否公平" + fair);
        long l = System.currentTimeMillis();

        int queueLength = semaphore.getQueueLength();
        System.out.println("等待執行緒數" + queueLength);
        Thread.sleep(100);
        Guid += 1;

        String format = new SimpleDateFormat("yyyy").format(l);

        if (Guid > 999){
            Guid = 100;
        }

        String id = lead + format + l + Guid;

        semaphore.release();
        return id;
    }
}
複製程式碼

注:文章源地址:https://www.cnblogs.com/jinggod/p/8494246.html


相關文章