淺談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 1 :
在使用synchronized關鍵字的情形下,假如占有鎖的線程由於要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,那么其他線程就只能一直等待,別無他法。這會極大影響程序執行效率。因此,就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間 (解決方案:tryLock(long time, TimeUnit unit)) 或者 能夠響應中斷 (解決方案:lockInterruptibly())),這種情況可以通過 Lock 解決。

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() : 釋放一個許可,將其返回給信號量。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM