簡體   English   中英

資源節省內存上的Java讀寫鎖

[英]Java Read Write Locks on a resource conserving memory

內存中有大量類型為R的對象。 修改對象需要寫鎖,而讀取則需要讀鎖。 我可以將ReadWriteLock存儲為類R的私有成員,但是,我想節省內存。 在任何時候,只有一小部分對象被修改或讀取。 有多種方法可以決定不存儲特定資源的讀寫鎖定(例如,如果在一定時間t內未對其進行讀寫)。 為了這個問題的目的,假設可以周期性地確定可以刪除資源的鎖。 但是,請記住,在線程中刪除資源鎖時,一個或多個其他線程可能會嘗試修改或讀取資源。 所有這些都是在多線程環境中發生的。 您將如何以最少的鎖定量實現這一目標?

例如,一種方法是將讀寫鎖存儲在並發映射中:

Map<R,ReadWriteLock> map = new ConcurrentHashMap<>();

當確定可以刪除資源的讀寫鎖后,將其從映射中刪除。 但是,如上所述,在決定刪除該條目之后和刪除該條目之前,其他線程可能希望獲取讀或寫鎖定。

您可能認為可以結合使用compatifabsentremove 但是,這不起作用。 例如:

//--Thread1 write lock--
ReadWriteLock rwl = map.computeIfAbsent(r, r -> new ReadWriteLock()); // 1
rwl.writeLock.lock();                                        // 4
//Modify r here

//--Thread2: Removing entry--
map.remove(r);                                               // 2

//Thread3: write lock
ReadWriteLock rwl = map.computeIfAbsent(r, r-> new ReadWriteLock()); // 3
rwl.writeLock.lock();                                        // 5
//Modify r here.

問題是線程1的鎖定對象與線程3的鎖定對象不同,並且錯誤地允許同時發生兩次寫入。 右邊的數字顯示執行順序。

答案不必使用上面示例中給出的並發映射,但這似乎是一個不錯的開始,並且可以並發訪問鎖。 如果您確實使用並發映射,則可以將ReadWriteLock包裝在另一個結構中,也可以創建自己的ReadWriteLock版本。

總而言之,問題是如何維護資源集合的讀寫鎖,而不必為集合中的每個對象存儲讀寫鎖並最大程度地減少鎖爭用。

問題是如何維護資源集合的讀寫鎖,而不必為集合中的每個對象存儲讀寫鎖並最大程度地減少鎖爭用

您是否考慮過使用條紋鎖? (例如, https : //google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Striped.html

基本上,它是M個數據的N個鎖的集合,其中N <M。哈希函數用於將任何給定數據的標識映射到控制它的鎖​​。

您可以使用computecomputeIfPresent方法compute computeIfPresent自己的優勢。 重要的是要在使用者內部進行添加/鎖定/刪除,以使其自動完成。

注意:您在示例中使用了putIfAbsent ,但是它返回了以前分配的值,而不是分配的值。

public static class Locks<R>
{
    private ConcurrentHashMap<R, ReentrantReadWriteLock> locks = new ConcurrentHashMap<>();

    public void lock(R r, Function<ReentrantReadWriteLock, Lock> whichLock)
    {
        locks.compute(r, (key, lock) -> {
            ReentrantReadWriteLock actualLock = lock == null ? new ReentrantReadWriteLock() : lock;
            whichLock.apply(actualLock).lock();
            return actualLock;
        });
    }

    public void unlock(R r, Function<ReentrantReadWriteLock, Lock> whichLock)
    {
        locks.computeIfPresent(r, (key, lock) -> {
            whichLock.apply(lock).unlock();
            return lock; // you could return null here if lock is unlocked (see cleanUp) to remove it immediately
        });
    }

    public void cleanUp()
    {
        for (R r : new ArrayList<>(locks.keySet()))
        {
            locks.computeIfPresent(r, (key, lock) -> locks.get(r).isWriteLocked()
                                                     || locks.get(r).getReadLockCount() != 0 ? lock : null);
        }
    }
}

注意我如何使用

  • compute lock以創建新鎖並立即將其鎖定
  • unlock computeIfPresent ,以檢查是否有鎖
  • cleanUp computeIfPresent ,用於在檢查讀取鎖計數時檢查是否需要一個鎖,而無需另一個線程鎖定寫鎖

目前, unlock是相當無用的(除了null檢查,這只是一個預防措施)。 unlock返回null會很好地清除不必要的鎖並使cleanUp過時,但可能會增加對創建新鎖的需求。 這取決於使用鎖的頻率。

當然,您可以為讀取/寫入添加方便的方法,而不必提供getter whichLock

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM