国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
讀寫鎖調優(yōu)緩存對象并發(fā)同步問題引申思考分析

業(yè)務問題

   最近調優(yōu)一個多線程使用共享Map對象本地緩存性能問題。原有實現(xiàn)背景為Map對象存儲從Redis加載的數(shù)據(jù),如果對應Redis數(shù)據(jù)為空,需要調用Redis加載邏輯,這段邏輯封裝在一個更新數(shù)據(jù)方法,并且加了同步鎖,實現(xiàn)線程安全。

示例代碼:
private Map<String,Object> cachMap = Maps.newHashMap();public synchronized void updateCache(Map<String,Object> getNewMap){		cachMap.clear();		cachMap.putAll(getNewMap);	}	public synchronized Map<String,Object> getCache(){		return cachMap;	}

 

我們都知道synchronized是實現(xiàn)共享對象同步處理的首選解決方法,在處理線程較少資源競爭下情況下確實性能不會有太大影響,但是現(xiàn)在我們面臨的場景是讀取cache的請求增加了10倍左右,寫入數(shù)據(jù)操作基本維持不變,應用性能明顯下降,排查發(fā)現(xiàn)synchronized是問題之一,因為它導致我們的cache訪問變成串行,影響整體鏈路并發(fā)執(zhí)行效率。

 

優(yōu)化方案

  1. 引入Java 5的讀寫鎖ReadWriteLock實現(xiàn)讀讀共享,讀寫保持互斥特征提升讀取性能。
         通過Lock可以實現(xiàn)讀讀共享、讀寫互斥、寫讀互斥、寫寫互斥。優(yōu)化示例如下:

private ReadWriteLock cacheRWLock = new ReentrantReadWriteLock();	public  void updateCache(Map<String,Object> getNewMap){		try {		cacheRWLock.writeLock().lock();//獲取寫鎖,唯一一個線程持有		cachMap.clear();		cachMap.putAll(getNewMap);		} catch (Exception e) {			// TODO: handle exception		}finally{			cacheRWLock.writeLock().unlock();//釋放寫鎖		}	}	public  Map<String,Object> getCache(){		try {			cacheRWLock.readLock().lock();//獲取讀鎖,多個線程可共享			return cachMap;		} catch (Exception e) {			// TODO: handle exception		}finally{			cacheRWLock.readLock().unlock();//釋放讀鎖,以免影響寫鎖		}		return null;	}

 

          分析代碼示例,變更共享對象Map前獲取寫鎖,如果獲取不到線程等待。獲取到寫鎖后 開始數(shù)據(jù)操作,數(shù)據(jù)操作之后要釋放寫鎖。而讀取對象Map前獲取讀鎖,同一時刻可以有多個線程獲取讀鎖從而達到并行的目的。對于同一個線程,如果獲取了寫鎖,同樣可以獲取讀鎖,也就是API描述的可重入性。讀鎖和寫鎖在ReadWriteLock 采用了不同的鎖機制,具體原理不表,API有詳細描述。

    2. 將HashMap替換為ConcurrentHashMap,實現(xiàn)細粒度線程安全同時提升并發(fā)性能。

      從場景中分析可知,我們需要處理的共享對象是HashMap實例,HashMap類在并發(fā)場景下非線程安全。但util.concurrent提供了線程安全的ConcurrentHashMap實現(xiàn),這個類的實現(xiàn)采用了是細粒度的hash segment維度的同步機制,也就是不同的segment對應不同的鎖,通過這種方式可以保持同步互斥下的并發(fā)處理能力。具體我們分析下ConcurrentHashMap的clear和putAll實現(xiàn)源碼。


public void clear() {        final Segment<K,V>[] segments = this.segments;        for (int j = 0; j < segments.length; ++j) {            Segment<K,V> s = segmentAt(segments, j);            if (s != null)                s.clear();        }}static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) {        long u = (j << SSHIFT) + SBASE;        return ss == null ? null :            (Segment<K,V>) UNSAFE.getObjectVolatile(ss, u);    }

 

   Clear的實現(xiàn)過程是遍歷map對象所有的segments,針對每個segment持有的volatile 定義的鎖對象判斷是否有同步鎖,如果都沒命中可以執(zhí)行清理。

public V put(K key, V value) {        Segment<K,V> s;        if (value == null)            throw new NullPointerException();        int hash = hash(key);        int j = (hash >>> segmentShift) & segmentMask;        if ((s = (Segment<K,V>)UNSAFE.getObject               (segments, (j << SSHIFT) + SBASE)) == null)             s = ensureSegment(j);        return s.put(key, hash, value, false);    }

 

     PutAll的實現(xiàn)基礎是put方法,基本原理是針對傳入數(shù)據(jù)key做hash,然后針對命中的segment判斷是否能獲取同步鎖,如果能獲取分配segments寫入,否則等待。

    對比兩種方案性能總體相差不大,測試case不表。

優(yōu)化衍生思考

     對于使用讀寫鎖優(yōu)化并發(fā)性能場景基于讀取量大于變更量的前提,如果讀取量跟變更量基本持平情況下就需要根據(jù)業(yè)務具體情況采用相應的策略,那有沒有通用的解決方案?

CopyOnWrite方案嘗試

     這種除了沿用Syncronized關鍵字實現(xiàn)重量級鎖,也可以采用volatile 實現(xiàn)內存共享實例,但是頻繁操作內存的成本較高不太適合。那是否可以結合volatile 和 ReentrantLock 實踐CopyOnWrite機制,實現(xiàn)CopyOnWriteArrayList相似功能(java.util.concurrent 沒有實現(xiàn)CopyOnWriteMap類,一直比較費解)。CopyOnWriteMap一種實現(xiàn)方式(源碼來源mongo-java-driver)可以拿出來分析一下,假設需要實現(xiàn)場景需要的clear和putAll方法代碼如下:

private volatile M delegate;// volatile定義的同步Map實例private final transient Lock lock = new ReentrantLock(); //定義讀寫鎖

 

    基于上述兩個關鍵同步變量定義,clear方法優(yōu)先獲取鎖,然后采用置換方法,把空Map對象置換變更delegate存儲的Map,基于volatile導致當前線程把數(shù)據(jù)從緩存中寫入內存中導致其他線程緩存失效的特性,保持數(shù)據(jù)變更的同步。

public final void clear() {        lock.lock();        try {            set(copy(Collections.<K, V> emptyMap()));        } finally {            lock.unlock();        }    }

 

    PutAll方法通用是獲取讀寫鎖,然后將寫入delegate存儲的舊Map對象copy出來,同時把新傳入的對象copy到中間Map對象,最后將中間Map對象寫入delegate。對應的delegate同步實現(xiàn)也交給JVM完成。操作完成后釋放讀寫鎖。

public final void putAll(final Map<? extends K, ? extends V> t) {        lock.lock();        try {            final M map = copy();            map.putAll(t);            set(map);        } finally {            lock.unlock();        }    }

 

    從這個實現(xiàn)分析,CopyOnWriteMap非常依賴volatile存儲的對象,而我們的緩存肯定是有限制的,所以這種方案存在一定局限性:需要限制處理Map大小,在使用前需要評估預估存儲量,否則過大map容易導致內存溢出。

本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
結合JDK源碼看設計模式——組合模式
設計模式-單例模式
Fast Read Map
Spring和Hibernate動態(tài)建表及動態(tài)加載映射文件(無需Session factory.
.NET LOCK方法使用
面試官和我扯了半個小時的synchronized,最后他輸了
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服