淺談synchronized、Lock、ThreadLocal和semaphore

淺談synchronized、Lock、ThreadLocal和semaphore - 格式化版本

 

1. 背景

在進行多線程編程時,最讓人頭痛的無非是線程安全問題,對共享資源的訪問控制,若是稍加不注意就可能致使莫名其名錯誤,主要體現有:

  • 建立單例對象時,內存中可能存在多個實例。
  • 一個線程正在讀取數據,因爲另外一個寫線程的介入,可能致使讀線程讀取到的數據髒亂不堪。
  • 同一對象可能同時被多個線程使用,形成結果上面的誤差

2. synchronized 的介紹

爲了防止多線程形成須要單例化的對象存在多實例問題,synchronized做爲懶漢式模式建立實例的常使用的關鍵字,使用以下:

private SocketManager() {
    }

    private static SocketManager INSTANCE;

    public static SocketManager getInstance() {
        if (INSTANCE == null) {
            synchronized (SocketManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SocketManager();
                }
            }
        }
        return INSTANCE;
    }

3. Lock的介紹

Lock是java中鎖操做接口,比synchronized使用上面更爲靈活。其主要實現類分爲ReentrantLock (重入鎖)和ReentrantReadWriteLock(讀寫鎖)。其中ReentrantLock(重入鎖)構造時,因爲布爾參數不一樣又分爲公平重入鎖和非公平重入鎖,其中非公平的重入鎖處理效率比公平重入鎖高,因此在建立時,通常使用ReentrantLock(false)。 另外一個ReentrantReadWriteLock專門用於對讀寫操做的加鎖(兩個讀線程不會衝突,兩個寫線程會衝突,一個讀一個寫線程會衝突,可是兩個讀線程不會衝突),若是ReentrantLock處理能力就不夠,再這個狀況下使用ReentrantLock。總之,通常狀況下,ReentrantLock基本就能處理問題,在讀寫上就能夠選擇使用ReentrantLock處理。

private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);
    //HashMap 非線程安全
    public static HashMap<Integer, String> pairs = new HashMap<>();


    public static void setPair(int key, String value) {
        reentrantReadWriteLock.writeLock().lock();
        pairs.put(key, value);
        reentrantReadWriteLock.writeLock().unlock();
    }

    public static String getValue(int key) {
        reentrantReadWriteLock.readLock().lock();
        String value = pairs.get(key);
        reentrantReadWriteLock.readLock().unlock();
        return value;
    }
  • 如下case引用於: 原博客地址html

    Case 1 :
    在使用synchronized關鍵字的情形下,假如佔有鎖的線程因爲要等待IO或者其餘緣由(好比調用sleep方法)被阻塞了,可是又沒有釋放鎖,那麼其餘線程就只能一直等待,別無他法。這會極大影響程序執行效率。所以,就須要有一種機制能夠不讓等待的線程一直無期限地等待下去(好比只等待必定的時間 (解決方案:tryLock(long time, TimeUnit unit)) 或者 可以響應中斷 (解決方案:lockInterruptibly())),這種狀況能夠經過 Lock 解決。java

Case 2 :
咱們知道,當多個線程讀寫文件時,讀操做和寫操做會發生衝突現象,寫操做和寫操做也會發生衝突現象,可是讀操做和讀操做不會發生衝突現象。可是若是採用synchronized關鍵字實現同步的話,就會致使一個問題,即當多個線程都只是進行讀操做時,也只有一個線程在能夠進行讀操做,其餘線程只能等待鎖的釋放而沒法進行讀操做。所以,須要一種機制來使得當多個線程都只是進行讀操做時,線程之間不會發生衝突。一樣地,Lock也能夠解決這種狀況 (解決方案:ReentrantReadWriteLock) 。編程

Case 3 :
咱們能夠經過Lock得知線程有沒有成功獲取到鎖 (解決方案:ReentrantLock) ,但這個是synchronized沒法辦到的。安全

4. ThreadLocal的介紹

前面講的都是在多線程狀況下,共享資源保持一致性,保證對象的惟一性和一致性。可是在某些情境中,同一對象須要在不一樣線程中相互獨立,即每個線程中都擁有該對象的一個副本。(PS: SimpleDateForma非線程安全)

// 測試代碼
public class Main {

    public static void main(String... args) {

        for (int i = 0; i < 5; i++) {
            new Thread() {
                @Override
                public void run() {
                    CountUtils.addCount();
                }
            }.start();
        }
    }
}
// 沒有使用ThreadLocal 
public class CountUtils {

    private static int countNum = 0;

    public static void addCount() {
        synchronized (CountUtils.class) {
            countNum++;
            System.out.println(Thread.currentThread().getName() + ":" + countNum);
        }
    }
}

// 輸出結果:
Thread-1:1
Thread-3:2
Thread-2:3
Thread-0:4
Thread-4:5
  • 靜態字段位於全局區,同時可以被多個線程修改。
public class CountUtils {

    private static ThreadLocal<Integer> integerThreadLocal = new InheritableThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void addCount() {
        synchronized (CountUtils.class) {
            int countNum = integerThreadLocal.get();
            countNum ++ ;
            System.out.println(Thread.currentThread().getName() + ":" + countNum);
        }
    }
}

// 輸出結果:

Thread-2:1
Thread-1:1
Thread-3:1
Thread-0:1
Thread-4:1
  • 總結: ThreadLocal採用Map<ThreadInfo,E>方式將線程操做的對象進行區分,不一樣的線程取值並不是同一個。

5. semaphore的介紹

semaphore (信號量) 控制線程的出入問題,建立該對象時指明可用的資源數(synchronized可用資源數爲1),當有資源空閒時,線程可進入,不然阻塞等待。項目中彈幕處理,維護彈幕池可用彈幕總數,當顯示的彈幕已經達到彈幕總數,信號量爲0,當某一彈幕移除屏幕,將彈幕控件放入彈幕控件池進行復用,並將信號量加1,定時器定時判斷信號量,當信號量不爲0時,從彈幕控制池取彈幕控件展現。

  • tryAcquire() : 僅在調用時此信號量存在一個可用許可,才從信號量獲取許可。
  • acquire() : 今後信號量獲取一個許可,在提供一個許可前一直將線程阻塞,不然線程被中斷。
  • release() : 釋放一個許可,將其返回給信號量。
相關文章
相關標籤/搜索