簡體   English   中英

Java線程安全 - 鎖定整個靜態類,但僅限於一種方法

[英]Java Thread Safety - Lock an entire static class but only for one method

我實現了一個靜態幫助程序類,可以幫助緩存和檢索數據庫中的一些只讀,不可變,非易失性數據。

(剝離)示例:

public class CacheHelper
{
    private static HashMap foos, bars;

    public static Foo getFoo(int fooId) { /* etc etc */ }
    public static Bar getBar(int barId) { /* etc etc */ }

    public static void reloadAllCaches()
    {
        //This is where I need it to lock access to all the other static methods
    }
}

我為靜態類讀取它的方法,如果我將synchronized關鍵字添加到reloadAllCaches()方法,這將在該方法執行時對整個類應用鎖。 它是否正確? (編輯:是的,不正確。感謝您的回復。)

注意:我想對getter方法的線程安全性以及它們返回的對象保持不可知,因為這些數據永遠不會發生變化,並且希望盡快返回它。

如果將synchronized關鍵字添加到reloadAllCaches()函數,則在reloadAllCaches()函數運行時,無法執行獲得synchronized關鍵字的類中的所有其他靜態函數。

非靜態函數如何執行,無論它們是否獲得synchronized關鍵字。 此外,沒有synchronized關鍵字的所有其他函數都可以執行。

畢竟具有synchronized的功能可以看作:

public class Bar
{
    public static void foo()
    {
        synchronized (Bar.class)
        {
            // your code
        }
    }
}

可以像這樣查看具有synchronized關鍵字的非靜態函數:

public class Bar
{
    public void foo()
    {
        synchronized (this)
        {
            // your code
        }
    }
}

因此,靜態和非靜態函數具有不同的同步上下文,並且不會使用synchronized關鍵字阻止彼此的執行。

對於您的情況,我建議使用ReentrantReadWriteLock 這個類允許任意數量的函數同時獲得讀鎖,但只有一個函數可以獲得Write-Lock。 只有在沒有讀鎖定的情況下才會獲取寫鎖定,並且只要寫鎖定到位就不會獲取讀鎖定。

您可以使重新加載函數獲取寫鎖定,並使所有讀取函數獲取寫鎖定。 您必須使用ReentrantReadWriteLock的靜態實例。

我的建議是像這樣實現它:

public class CacheHelper
{
    private static HashMap foos, bars;
    private static java.util.concurrent.locks.ReadWriteLock lock = new java.util.concurrent.locks.ReentrantReadWriteLock();

    public static Foo getFoo(int fooId)
    {
        lock.readLock().lock();
        try {
            /* etc etc */
        } finally {
            lock.readLock().unlock();
        }
    }
    public static Bar getBar(int barId)
    {
        lock.readLock().lock();
        try {
            /* etc etc */
        } finally {
            lock.readLock().unlock();
        }
    }

    public static void reloadAllCaches()
    {
        lock.writeLock().lock();
        try {
            //This is where I need it to lock access to all the other static methods
        } finally {
            lock.writeLock().unlock();
        }
    }
}

不,這不正確。 僅對reloadAllCaches方法添加synchronized意味着該方法的調用者必須獲取該類的鎖,但調用非同步方法的線程仍然可以同時訪問該類。 您仍然需要在同一個鎖上同步訪問器才能確保安全,否則閱讀器線程可能看不到最新的更改並將獲得過時的數據。 或者,您可以使用ConcurrentHashMap。

如果您希望能夠在不鎖定集合的情況下重新填充集合,則可以使用不可變集合替換它們。

private static volatile Map foos, bars;

public static Foo getFoo(int fooId) { return foos.get(fooId); }
public static Bar getBar(int barId) { /* etc etc */ }

public static void reloadAllCaches()
{
    Map newFoo = ...
    // populate newFoo
    foos = newFoo;

    Map newBar = ...
    // populate newBar
    bars = newBar;
}

getFoo將看到一個完整的一致副本,而不需要鎖定,因為Map總是被替換,永遠不會被修改。


synchronized鎖對象不是方法,在這種情況下,您將鎖定CacheHelper.class對象

要盡可能快地獲取getter,可以使用ConcurrentHashMap而不是使用synchronized


僅用於更新的同步的示例。

final ConcurrentMap<Key, ExpensiveObject> map =

public ExpensiveObject getOrNull(Key key) { 
     return map.get(key); 
}

public ExpensiveObject getOrCreate(Key key) {
     synchronized(map) {
         ExpensiveObject ret = map.get(key);
         if (ret == null)
              map.put(key, ret = new ExpensiveObject(key));
         return ret;
     }
}

您可以在reloadAllCaches()CacheHelper類對象(CacheHelper.class)應用鎖定,而不是在某個代碼段中將此鎖定應用於此方法,因為我看到的所有方法都是static ,如果使它們全部synchronized則所有線程都將被阻塞如果任何線程正在訪問任何方法。

簡單來說,鎖只能防止其他線程同時運行相同的方法,它不會提供任何鎖定資源而不是類中的任何其他內容,靜態或其他。

其他線程將阻止訪問該方法,直到具有控件的線程退出該方法。 所有線程仍可免費訪問其他任何內容。

如果您需要鎖定對象本身的控制權,那么您需要考慮為緩存提供線程安全訪問器或某種繼承處理。

我的意思是,如果你在這個方法中構造一個新的緩存,並且一旦構造用這些新對象替換緩存助手中的引用對象,那么只需要同步reloadAllCaches方法就可以了。

但是,如果您的目的是重用/回收現有緩存容器,則必須在容器級別使用鎖定以防止在緩存被銷毀和重建時進行讀取。

如果您需要重新加載多個緩存的映射(根據您的示例),那么您可能會發現有必要將緩存的對象抽象出另一層,否則您可能會在重新應用緩存時失去對緩存的同步訪問權限。

暫無
暫無

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

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