信號量Semaphore和線程池的差異

信號量Semaphore是一個併發工具類,用來控制可同時併發的線程數,其內部維護了一組虛擬許可,通過構造器指定許可的數量,每次線程執行操作時先通過acquire方法獲得許可,執行完畢再通過release方法釋放許可。如果無可用許可,那麼acquire方法將一直阻塞,直到其它線程釋放許可。

線程池用來控制實際工作的線程數量,通過線程複用的方式來減小內存開銷。線程池可同時工作的線程數量是一定的,超過該數量的線程需進入線程隊列等待,直到有可用的工作線程來執行任務。

使用Seamphore,你創建了多少線程,實際就會有多少線程進行執行,只是可同時執行的線程數量會受到限制。但使用線程池,你創建的線程只是作爲任務提交給線程池執行,實際工作的線程由線程池創建,並且實際工作的線程數量由線程池自己管理。

簡單來說,線程池實際工作的線程是work線程,不是你自己創建的,是由線程池創建的,並由線程池自動控制實際併發的work線程數量。而Seamphore相當於一個信號燈,作用是對線程做限流,Seamphore可以對你自己創建的的線程做限流(也可以對線程池的work線程做限流),Seamphore的限流必須通過手動acquire和release來實現。

區別就是兩點:

  • 使用線程池,實際工作線程由線程池創建;使用Seamphore,實際工作的線程由你自己創建
  • 限流是否自動實現:線程池自動,Seamphore手動

Semaphore

  public static void testSeamphore() {
    Semaphore semaphore = new Semaphore(2);
    for (int i = 0; i < 5; i++) {
      Thread thread = new Thread() {
        public void run() {
          try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " start running **********************");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " stop running  ----------------------");
            semaphore.release();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };
      thread.setName("my thread " + i);
      thread.start();
    }
  }


//// 結果,創建了5個線程
my thread 0 start running **********************
my thread 1 start running **********************
my thread 0 stop running  ----------------------
my thread 1 stop running  ----------------------
my thread 2 start running **********************
my thread 3 start running **********************
my thread 3 stop running  ----------------------
my thread 2 stop running  ----------------------
my thread 4 start running **********************
my thread 4 stop running  ----------------------

線程池

  public static void testPool() {
    ExecutorService executorService = new ThreadPoolExecutor(2, 5,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());
 
    for (int i = 0; i < 5; i++) {
      Thread thread = new Thread() {
        public void run() {
          try {
            System.out.println(Thread.currentThread().getName() + " start running **********************");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " stop running  ----------------------");
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };
      thread.setName("my thread " + i);
      executorService.submit(thread);
    }
    executorService.shutdown();
  }

//// 結果,只會創建兩個線程
pool-1-thread-1 start running **********************
pool-1-thread-2 start running **********************
pool-1-thread-2 stop running  ----------------------
pool-1-thread-1 stop running  ----------------------
pool-1-thread-1 start running **********************
pool-1-thread-2 start running **********************
pool-1-thread-1 stop running  ----------------------
pool-1-thread-2 stop running  ----------------------
pool-1-thread-1 start running **********************
pool-1-thread-1 stop running  ----------------------

Semaphore作爲互斥鎖的體現

Semaphore實現互斥鎖的方式是使用初始值爲1的Semaphore對象,這樣每條線程獲取許可後必須釋放許可,其它線程才能獲取許可,當前擁有許可的線程就擁有了互斥鎖。

    public static void testMutex() {
        Semaphore semaphore = new Semaphore(1);
        for (int i = 0; i < 5; i++) {
            new Thread() {
                public void run() {
                    try {
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "已獲得許可");
                        Thread.sleep(2000);
                        System.out.println(Thread.currentThread().getName() + "已釋放許可");
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

//// 結果
Thread-0已獲得許可
Thread-0已釋放許可
Thread-1已獲得許可
Thread-1已釋放許可
Thread-2已獲得許可
Thread-2已釋放許可
Thread-3已獲得許可
Thread-3已釋放許可
Thread-4已獲得許可
Thread-4已釋放許可

Semaphore先release後acquire

Seamphore有一種特殊的使用場景,即先釋放許可,後申請許可,此時會額外增加一個許可。

實際編程中要額外小心,如下的實例,通過new Semaphore(0)創建的信號量,默認許可數是0,如果先調用release,會增加一個許可,再次acquire便可以獲取新增的許可。

public static void main(String[] args) throws InterruptedException {
    Semaphore semaphore = new Semaphore(0);
    System.out.println(semaphore.availablePermits());
    semaphore.release();
    System.out.println(semaphore.availablePermits());
    semaphore.acquire(); //阻塞
    System.out.println(semaphore.availablePermits());
}

//// 結果
0
1
0

所以上面的代碼實際上不會發生阻塞,而是直接輸出0 1 0。本例中如果將release和acquire調換位置,則一定會發生阻塞。

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