[英]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.