併發工具類——Semaphore

本博客系列是學習併發編程過程當中的記錄總結。因爲文章比較多,寫的時間也比較散,因此我整理了個目錄貼(傳送門),方便查閱。html

併發編程系列博客傳送門java


Semaphore([' seməf :(r)])的主要做用是控制線程併發的數量。咱們能夠將Semaphore想象成景區的一個門衛,這個門衛負責發放景區入園的許可證。編程

景區爲了遊客的入園觀賞體驗,決定最多容許200個有個同時在園內觀賞。那麼這個門衛在天天開園的時候手中都會有200張許可證,每當一個遊客要入園的時候門衛會給遊客發放一張許可證,當門衛手中的許可證發完以後再有遊客須要入園的話就必須等待。api

當遊客觀賞完畢以後,出園的時候須要將許可證交還到門衛手上。門衛將這些交還的許可證再發等待的遊客,這些遊客就能順利入園了。數組

Semaphore的API簡介

Semaphore的API使用起來也比較簡單,常見的API簡介以下:安全

  • Semaphore(int permits):構造方法,建立具備給定許可數的計數信號量並設置爲非公平信號量。
  • Semaphore(int permits,boolean fair):構造方法,當fair等於true時,建立具備給定許可數的計數信號量並設置爲公平信號量。
  • void acquire():今後信號量獲取一個許可前線程將一直阻塞。至關於一輛車佔了一個車位。
  • void acquire(int n):今後信號量獲取給定數目許可,在提供這些許可前一直將線程阻塞。好比n=2,就至關於一輛車佔了兩個車位。
  • boolean tryAcquire(int permits, long timeout, TimeUnit unit):嘗試獲取,在給定的時間內沒獲取到資源超時
  • void release():釋放一個許可,將其返回給信號量。就如同車開走返回一個車位。
  • void release(int n):釋放n個許可。
  • int availablePermits():當前可用的許可數。

Semaphore的常見用法

下面給出一個Oracle官方文檔中的列子代碼:併發

class Pool {
    // 可同時訪問資源的最大線程數
    private static final int MAX_AVAILABLE = 100; 
    private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
    // 共享資源
    protected Object[] items = new Object[MAX_AVAILABLE];   
    protected boolean[] used = new boolean[MAX_AVAILABLE];
    public Object getItem() throws InterruptedException {
        available.acquire();
        return getNextAvailableItem();
    }
    public void putItem(Object x) {
        if (markAsUnused(x))
            available.release();
    }
    private synchronized Object getNextAvailableItem() {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (!used[i]) {
                used[i] = true;
                return items[i];
            }
        }
        return null;
    }
    private synchronized boolean markAsUnused(Object item) {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (item == items[i]) {
                if (used[i]) {
                    used[i] = false;
                    return true;
                } else
                    return false;
            }
        }
        return false;
    }
}

items數組能夠當作是咱們的共享資源,當有線程嘗試使用共享資源時,咱們要求線程先得到「許可」(調用Semaphoreacquire方法),這樣線程就擁有了權限,不然就須要等待。當使用完資源後,線程須要調用Semaphorerelease方法釋放許可。dom

Semaphore並不能替代synchronized

有些書中提到:若是將Semaphore的許可證數量設置成1的話,就能實現synchronized的功能。其實這種說法是不對的。ide

下面使用Semaphore來控制對一個帳戶進行併發存錢和取錢的動做,若是Semaphore能實現synchronized的功能的話,帳戶最後的餘額應該仍是10000,但代碼執行後的結果並非這樣。你們能夠執行下面的代碼看下結果。學習

public static final int THREAD_COUNT = 100;

    public static void main(String[] args) {
        BankAccount myAccount = new BankAccount("accountOfMG", 10000.00);
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int var = new Random().nextInt(100);
                        Thread.sleep(var);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    double deposit = myAccount.deposit(1000.00);
                    System.out.println(Thread.currentThread().getName() + " balance1:" + deposit);
                }
            }).start();
        }
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int var = new Random().nextInt(100);
                        Thread.sleep(var);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    double deposit = myAccount.withdraw(1000.00);
                    System.out.println(Thread.currentThread().getName() + " balance2:" + deposit);

                }
            }).start();
        }
    }

    private static class BankAccount {

        Semaphore semaphore = new Semaphore(1);

        String accountName;
        double balance;

        public BankAccount(String accountName, double balance) {
                this.accountName = accountName;
                this.balance = balance;
        }

        public double deposit(double amount) {
            try {
                semaphore.acquire();
                balance = balance + amount;
                return balance;
            } catch (Exception e) {
                throw new RuntimeException("中斷...");
            } finally {
                semaphore.release();
            }
        }

        public double withdraw(double amount) {
            try {
                semaphore.acquire();
            balance = balance - amount;
            return balance;
            } catch (Exception e) {
                throw new RuntimeException("中斷...");
            } finally {
                semaphore.release();
            }
        }

    }

這裏Semaphore並不能實現synchronized的功能的緣由是:Semaphore並不能保證共享變量的可見性。

實現原理

Semaphore底層原理仍是基於AQS機制的。這邊就不具體分析了,感興趣的能夠參考我前面關於AQS的文章

簡單總結

  • Semaphore只能用來作線程同步——控制線程的執行順序,可是並不能保證線程安全;
  • Semaphore主要用來控制線程的併發數量,一般用在限流組件中。
  • Semaphore基於AQS機制實現。
相關文章
相關標籤/搜索