Java經常使用多線程輔助工具---semaphore

前言

    semaphore是經常使用的Java多線程工具之一,中文意思是信號的意思。此類的主要做用是限制線程併發的數量,經過發放許可方式控制線程數量,能夠說該類是synchronized關鍵字的升級版本,比synchronized功能更強大。java

入門

    經過semaphore實現一個線程的同步數據庫

@Slf4j
class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service){
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service){
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

class ThreadC extends Thread{
    private Service service;
    public ThreadC(Service service){
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}
@Slf4j
class Service{
    private Semaphore semaphore = new Semaphore(1);
    public void testMethod(){
        try {
            semaphore.acquire();
            log.info("線程名稱{},beginTime:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            Thread.sleep(5000);
            log.info("線程名稱{},endTime:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            semaphore.release();
        } catch (InterruptedException e) {
            log.error("線程錯誤{}",e.getMessage());
        }
    }
}

上面寫了三個線程調用使用semaphore的服務,下面把線程啓動一下:多線程

public static void main(String[] args) {
        Service service = new Service();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        ThreadC c = new ThreadC(service);
        c.setName("C");

        a.start();
        b.start();
        c.start();
    }

    我是經過main方法去啓動的線程,若是用junit去啓動的話,發現線程會啓動不了(如今尚未搞懂是什麼緣由)。併發

    上面經過只用了一個許可,覺得這隻容許一個線程執行acquire和release之間的代碼,因此不管執行多少遍,結果都是下面的結果(相似於同步):ide

經常使用API

    類Semaphore的構造參數permit設置是許可的個數,上面的例子是在一個許可下的狀況,若是多個許可的話,那麼acquire和release之間能夠同時容許多個線程執行,其實構造參數中設置的許可只是初始化的許可,若是咱們作下面這個操做:函數

public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(5);
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();

        log.info("可用的許可有多少:{}",semaphore.availablePermits());
        semaphore.release();
        semaphore.release();
        semaphore.release();
        semaphore.release();
        semaphore.release();
        semaphore.release();
        log.info("可用的許可有多少:{}",semaphore.availablePermits());
        semaphore.release(4);
        log.info("可用的許可有多少:{}",semaphore.availablePermits());
    }

    看看運行結果:工具

    發現可用的許可愈來愈多,可見經過release(int)是能夠動態增長許可的。ui

    acquireUninterruptibly指的是獲取的許可不容許被終端,在線程運行的時候,若是調用線程的interrupt,線程並不會拋出異常,並中止運行。this

    availablePermits:能夠獲取當前可用的許可還有多少個。spa

    drainPermits:可獲取並返回當即可用的全部許可個數,而且將可用的許可置0。

    getQueueLength:獲取等待許可的線程個數。

    hasQueuedThreads:判斷當前有沒有線程在等待這個許可。

    剛剛在開始的那個案例中,咱們發現線程運行的順序是A、B、C,其實線程的啓動順序是abc,形成的緣由是許可默認是公平的,即先啓動的線程大機率先拿到許可,咱們能夠經過在構造函數的第二個參數設置是否公平許可:

    開啓非公平許可後的執行結果是下面這樣的:

    tryAcquire:無阻塞的嘗試獲取許可,若是獲取不到就返回false,程序繼續往下走。

實現一個字符串池

    semaphore既然能夠控制併發 的數量,這種功能能夠用在pool技術中,好比一個字符串池,若干個線程能夠同時訪問池中的數據,可是同時只有其中的幾個能夠得到數據,使用完畢之後再放回池中,不少pool技術的實現都是這種思路吧,開始上代碼,因此建立一個池:

class StrPool{
    private int poolSize = 3;
    private Semaphore semaphore = new Semaphore(10); //許可數量設置成和池大小一致
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private List<String> pool = new ArrayList();
    public StrPool() {
        for (int i = 0; i < poolSize; i++) {
            pool.add("str---" + (i + 1));
        }
    }

    public String get(){
        String str = null;
        try {
            semaphore.acquire(); //獲取許可
            lock.lock(); //同步鎖
            while (pool.isEmpty()) {
                System.out.println("------is waiting now ------- ");
                condition.await(); //若是池中沒有字符串了,那就線程掛着,等有人放進新的字符串後 喚醒該線程
            }
            str = pool.remove(0);
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return str == null ? "" : str;
    }
    public void put(String str){
        lock.lock();
        pool.add(str);
        condition.signalAll(); //若是有在等待的線程,進行喚醒
        lock.unlock();
        semaphore.release();
    }
}

特地把許可數設置成大於池的大小,爲了觀察是否是有的線程進入了等待狀態,咱們再起寫個線程去操做字符串池:

@Slf4j
class PoolThread extends Thread{
    private StrPool pool;
    public PoolThread(StrPool pool) {
        this.pool = pool;
    }

    @Override
    public void run() {
        String str = pool.get();
        log.info("線程{},獲取了池中的數據{}",Thread.currentThread().getName(),str);
        pool.put(str);
    }
}

啓動20個線程,看看運行結果:

public static void main(String[] args) throws InterruptedException {
        StrPool strPool = new StrPool();
        PoolThread[] threads = new PoolThread[20];
        for (int i = 0; i < 20; i++) {
            threads[i] = new PoolThread(strPool);
        }
        for (int i = 0; i < 20; i++) {
            threads[i].start();
        }
    }

咱們看到一進來就有的線程就在的等待狀態了。而後獲取的結果就是池中的 1 到 3編號的字符串:

咱們經常使用的數據庫鏈接池,其實能夠這麼實現,原理大同小異。

相關文章
相關標籤/搜索