Semaphore和線程池的差異

1. 什麼是Semaphore和線程池

Semaphore稱爲信號量,是java.util.concurrent一個併發工具類,用來控制可同時併發的線程數,其內部維護了一組虛擬許可,通過構造器指定許可的數量。線程在執行時,需要通過acquire()獲得許可後才能執行,如果無法獲得許可,則線程將一直等待;線程執行完後需要通過release()釋放許可,以使得其他線程可以獲得許可。

線程池也是一種控制任務並和執行的方式,通過線程複用的方式來減小頻繁創建和銷燬線程帶來的開銷。一般線程池可同時工作的線程數量是一定的,超過該數量的線程需進入線程隊列等待,直到有可用的工作線程來執行任務。

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

2. Semaphore和線程池的區別

先亮結果,Semaphore和線程池的區別如下:

  • 使用Semaphore,實際工作線程由開發者自己創建;使用線程池,實際工作線程由線程池創建
  • 使用Semaphore,併發線程的控制必須手動通過acquire()release()函數手動完成;使用線程池,併發線程的控制由線程池自動管理
  • 使用Semaphore不支持設置超時和實現異步訪問;使用線程池則可以實現超時和異步訪問,通過提交一個Callable對象獲得Future,從而可以在需要時調用Future的方法獲得線程執行的結果,同樣利用Future也可以實現超時

接下來用示例說明結果:

1. 使用Semaphore

public static void testSemaphore() {
    Semaphore semaphore = new Semaphore(2);
    for (int i = 0; i < 6; 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("Semaphore thread " + i);
        thread.start();
    }
}

輸出結果如下:

Semaphore thread 0 start running
Semaphore thread 1 start running
Semaphore thread 0 stop running
Semaphore thread 1 stop running
Semaphore thread 2 start running
Semaphore thread 3 start running
Semaphore thread 3 stop running
Semaphore thread 2 stop running
Semaphore thread 4 start running
Semaphore thread 5 start running
Semaphore thread 5 stop running
Semaphore thread 4 stop running

通過輸出可以發現:

  • 每次最多打印兩個start running記錄,因爲Seamphore的信號量是2
  • 只有當其他線程釋放了Seamphore後,新的線程才能開始執行
  • 線程的名字以 Semaphore thread開頭,且每個執行線程的名字都不相同

2. 使用線程池

public static void testThreadPool() {
    ExecutorService executorService = new ThreadPoolExecutor(2, 5,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());

    for (int i = 0; i < 6; 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("ThreadPool thread " + i);
        executorService.submit(thread);
    }
    executorService.shutdown();
}

輸出結果如下:

pool-1-thread-2 start running
pool-1-thread-1 start running
pool-1-thread-1 stop running
pool-1-thread-2 stop running
pool-1-thread-2 start running
pool-1-thread-1 start running
pool-1-thread-2 stop running
pool-1-thread-1 stop running
pool-1-thread-2 start running
pool-1-thread-1 start running
pool-1-thread-1 stop running
pool-1-thread-2 stop running

通過輸出可以發現:

  • 每次最多打印兩個start running記錄,因爲線程池的核心容量是2,多餘的線程任務放到阻塞隊列等待
  • 兩個線程的名字,即pool-1-thread-1和pool-1-thread-2,不是開發者自己創建的,而是線程池創建的
  • 任務的執行和結束都是由線程池來完成,開發者只需要將任務提交給線程池即可

3. 用Semaphore實現互斥鎖

使用信號值爲1的Semaphore對象便可以實現互斥鎖,示例如下:

public static void testSemaphoreMutex() {
    Semaphore semaphore = new Semaphore(1);
    for (int i = 0; i < 6; i++) {
        new Thread() {
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " get semaphore");
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + " release semaphore");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

輸出結果如下:

Thread-0 get semaphore
Thread-0 release semaphore
Thread-1 get semaphore
Thread-1 release semaphore
Thread-2 get semaphore
Thread-2 release semaphore
Thread-3 get semaphore
Thread-3 release semaphore
Thread-4 get semaphore
Thread-4 release semaphore
Thread-5 get semaphore
Thread-5 release semaphore

可以看出,任何一個線程在釋放許可之前,其它線程都拿不到許可。這樣當前線程必須執行完畢,其它線程纔可執行,這樣就實現了互斥。

4. Semaphore的易錯點

使用Semophore時有一個非常容易犯錯誤的地方,即先release再acqure後會導致Semophore管理的虛擬許可額外新增一個,示例如下:

public static void firstReleaseThenAcquire() throws InterruptedException {
    Semaphore semaphore = new Semaphore(1);
    System.out.println("Init permits: " + semaphore.availablePermits());
    semaphore.release();
    System.out.println("Permits after first releasing:" + semaphore.availablePermits());
    semaphore.acquire();
    System.out.println("Permits after first acquiring:" + semaphore.availablePermits());
    semaphore.acquire();
    System.out.println("Permists after second acquiring:" + semaphore.availablePermits());
}

輸出結果如下:

Init permits: 1
Permits after first releasing:2
Permits after first acquiring:1
Permists after second acquiring:0

可以發現,雖然Semophore的初始信號量爲1,但是當先調用release()後,Semophore的信號量變爲2了,因此才能夠連續調用兩次acquire()都能獲得許可。

Github博客地址
知乎

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