Java Semphore信號量的使用

個人博客請訪問 http://www.x0100.top        

前言:在多線程環境的同步中,我們爲了讓每個線程具有同步的作用,經常採用synchronize、reetrantlock等同步手段進行上鎖,以便在同一時間只能有一個線程具有訪問變量和讀寫變量的權力。然而假如實際的業務場景是允許一組線程訪問(組線程數量有限),如何控制一組線程的同步,如果再採取加鎖的方法就有點過猶不及了。那麼此時信號量就閃亮登場了,對於一組線程的同步訪問,對它來說就是小菜一碟。

目錄

「一:semphore的簡介」

「二:semphore的使用方法」

「三:使用實例」

「四:總結」

一:semphore的簡介

1.1:概念

semphpore是jdk提供的一個併發工具類,它位於java.util.concurrent包下,在jdk中對它是這樣定義的:

一個計數信號量。從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動,Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。**簡單解釋一下這段概念:它就是說semphpore可以對一組線程進行限定,線程每次訪問程序之前必須通過它的acquire()方法進入一個房間,也就是沒有調用 acquire()方法,線程是無法讀取程序的,然後再通過release()方法釋放這個線程,其他線程後面才能進入,而它是否允許是通過計數來完成的。

1,.2:理解

舉個通俗的例子,假如我們要從出發地A到目的地B,有一輛車,它只能容納4個人,
而我們一共有10個人要從A到B,這輛車就好比是資源,
而人乘車這一行爲就是線程訪問資源,
我們進入車需要車票,車票就可以理解爲信號量,
一次只能進入4個人,其他人只能等待(線程wait,處於阻塞狀態),
而到了目的地或者有人中途下車了,此時就是release(),
釋放信號量,那麼等待的人才會獲得允許上車,每次進入的時候,都會進行計數。

二:semphore的使用方法

2.1:acquire()方法

  acquire()      從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷。
   
  acquire(int permits)      從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。
   
 acquire方法提供了兩種不同的參數用來重載,
 它主要用於來控制線程是否允許從阻塞狀態到活動狀態,
 有點類似於進入高速公路前的“通行證”,
 只有調用了這個方法,線程才能從阻塞狀態變爲被喚醒。
 而acquire(int permits),
 則提供了指定數量的線程數用來允許此信號量獲取一次進入的數量,
 這個方法比較常用,它經常被用於控制一組線程進行訪問資源

「2.2:release()方法」

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

release方法和acquire方法相對應,通acquire方法獲取了通行證,那麼當使用完資源的時候,出去的時候就要調用release方法進行釋放鎖,這樣其他線程纔有資格再調用acquire方法再次獲取"通行證”。同樣它提供了重載的方法可以允許一次釋放很多線程。

三:使用實例

3.1:現在來模擬10個人乘車,一輛車只能容納4個人,所以一次只能進入4個人,而其他人只能處於阻塞狀態,只有獲取許可,才能進入車中,當一個人下來的時候其他人才能進入繼續乘車

3.2:程序實例

3.2.1:我們先來模擬一個人,定義它的行爲乘車和下車,比較簡單,下面給出示例代碼:

public class Person {

    private static final String suffix="號開始乘車";

    private static final String suffix2="號出來了";

    /**
     * 編號
     */
    private Integer no;

    public Person(Integer no) {
        this.no = no;
    }

    public void riding(){

        StringBuilder stringBuilder = new StringBuilder();

        System.out.println(stringBuilder.append(this.getNo()).append(suffix));

    }

    public void out(){

        StringBuilder stringBuilder = new StringBuilder();

        System.out.println(stringBuilder.append(this.getNo()).append(suffix2));

    }


    public Integer getNo() {
        return no;
    }

    public void setNo(Integer no) {
        this.no = no;
    }
}

3.2.2:再來定義一個乘車的線程,擁有信號量和人兩個引用:

public class RideThread implements Runnable {

    private Semaphore semp;

    private Person person;

    public RideThread(Semaphore semp, Person person) {
        this.semp = semp;
        this.person = person;
    }

    public void run() {

        try {

            // 獲取許可
            semp.acquire();

            person.riding();

            Thread.sleep((long) (Math.random() * 10000));

            person.out();
            // 訪問完後,釋放
            semp.release();


        } catch (InterruptedException e) {

            e.printStackTrace();

        }
    };
}

3.2.3:測試類

public class SemphoreTest {

    public static final Integer personNums=10;

    public static void main(String[] args) {
        // 線程池
        ExecutorService exec = Executors.newCachedThreadPool();

        // 只能4個人能同時上車
        final Semaphore semp = new Semaphore(4);

        // 模擬10個人乘車
        for (int index = 0; index < personNums; index++) {

            final int NO = index;

            Person person = new Person(NO);

            exec.submit(new RideThread(semp,person));

        }

        System.out.println(semp.isFair());

        // 退出線程池

        exec.shutdown();
    }
}

3.3:輸出結果

"D:\Java\jdk 1.8.0_1\bin\java" -Didea.launcher.port=7532 "-Didea.launcher.bin.path=D:\IntelliJ IDEA 2016.3.4\bin" -Dfile.encoding=GBK -classpath "D:\Java\jdk 1.8.0_1\jre\lib\charsets.jar;D:\Java\jdk 1.8.0_1\jre\lib\deploy.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\access-bridge-32.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\cldrdata.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\dnsns.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\jaccess.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\jfxrt.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\localedata.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\nashorn.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\sunec.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\sunmscapi.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk 1.8.0_1\jre\lib\ext\zipfs.jar;D:\Java\jdk 1.8.0_1\jre\lib\javaws.jar;D:\Java\jdk 1.8.0_1\jre\lib\jce.jar;D:\Java\jdk 1.8.0_1\jre\lib\jfr.jar;D:\Java\jdk 1.8.0_1\jre\lib\jfxswt.jar;D:\Java\jdk 1.8.0_1\jre\lib\jsse.jar;D:\Java\jdk 1.8.0_1\jre\lib\management-agent.jar;D:\Java\jdk 1.8.0_1\jre\lib\plugin.jar;D:\Java\jdk 1.8.0_1\jre\lib\resources.jar;D:\Java\jdk 1.8.0_1\jre\lib\rt.jar;E:\Elas Search\Test\out\production\Test;D:\IntelliJ IDEA 2016.3.4\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain Semphore.SemphoreTest
1號開始乘車
0號開始乘車
3號開始乘車
2號開始乘車
false
3號出來了
4號開始乘車
2號出來了
5號開始乘車
4號出來了
6號開始乘車
5號出來了
7號開始乘車
1號出來了
8號開始乘車
8號出來了
9號開始乘車
7號出來了
0號出來了
6號出來了
9號出來了

可以看出來最開始先進入4個人乘車,首先4個人獲取了許可證,然後後面的都是一個出來,另一個進去,只有當一個線程獲取信號量再釋放信號量的時候,其它線程才能乘車。這樣按照順序,嚴格限定每次只有空位的時候其他線程才能訪問資源!觀察結果,會發現最開始的4個是雜序的(後面進入的順序是for循環控制的),這是“非公平的”,因爲在構造Semphore的時候,沒有限定第二個參數isFair,這樣默認是非公平的,符合按照順序來進行線程的訪問,假如要公平的話,我們可以指定第二個參數爲true,這樣構造出來的Semhore就是公平的,很多線程去搶,誰搶到是誰的,我們把第二個參數設置爲true來觀察一下輸出的結果:

是否是公平鎖:true
0號開始乘車
1號開始乘車
2號開始乘車
3號開始乘車

執行了很多次,都可以看出來最開始的線程是按照順序進行的,這就是公平信號量,嚴格遵守順序依次執行!

「四:總結」

本篇博客講述了Semphore的使用方法,只是拋磚引玉簡單的闡述了幾個重要的方法和基本使用,在實際的開發中,會遇到更加複雜的業務場景,如何選擇jdk提供給我們的便捷的開發工具,在併發中做到沒有髒數據,高效、穩定是我們每個開發者都將追求的目標。

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