我正在嘗試將同步塊用于我需要在以下代碼中使用它的用例。
public void saveInCatch(String reqId,String desc) {
synchronized (reqId) {
cache.add(reqId,desc);
}
}
從我的應用程式的許多地方呼叫此方法。如果相同,我想確保代碼塊一次只能由一個執行緒訪問reqId。如果不同,則可以同時訪問該塊。我怎樣才能做到這一點?
uj5u.com熱心網友回復:
所以這里的問題是確保在同一個請求 id 上同步的兩個或多個執行緒對于這個代碼塊獲得互斥。
人們可能認為代碼已經這樣做了,但事實并非如此。問題是兩個String物件可能相等,Object.equal(Object)但不是同一個物件……根據==。如果不同的執行緒執行緒鎖定相同請求 ID 的不同副本,您的代碼不會根據請求 ID 值給您互斥。
...您可能已經意識到...這就是您問這個問題的原因!
一種解決方案是簡單地intern請求 ID;例如
public void saveInCatch(String reqId, String desc) {
synchronized (reqId.intern()) {
cache.add(reqId, desc);
}
}
實習為您提供了請求字串的“規范”版本。通過鎖定規范字串,您保證在請求 id 上獲得互斥。
這種方法有幾個缺點:
如果你經常這樣做,你可能會填滿 JVM 的字串池:
- 在 Java 7 之前,這可能會在最壞的情況下導致 permgen 耗盡,或大量“完整”垃圾回收。
- 在 Java 7 中,字串池被移到了常規堆中,并且不再可訪問的內部字串將被收集。但是,仍然存在與實習字串相關的大量記憶體和垃圾收集開銷。
如果您的應用程式的兩個部分出于不同目的執行此類操作,則您可能會在不同資源類別的鎖之間遇到鎖干擾。
第二種方法是使用鎖分條。您可以手動實作(如@pveentjer建議Striped的那樣),或者您可以使用 Guava類(javadoc);例如
private static final Striped<Lock> locks = Striped.lock(nosStripes);
...
public void saveInCatch(String reqId, String desc) {
Lock lock = locks.get(reqId);
try {
lock.lock();
cache.add(reqId, desc);
} finally {
lock.unlock();
}
}
The downside of lock striping is that you are more likely to get lock interference, due to different request ids mapping to the same lock. But the upside is that the memory and GC overheads are bounded by the (constant) number of stripes you use.
Finally, as noted by @jtahlborn, you are probably reinventing the wheel. There are existing open source concurrent cache classes that you could use, or you could use ConcurrentHashMap with some kind of cleaner.
uj5u.com熱心網友回復:
問題是synchronized(Object)需要一個唯一的物件。
但是,以下兩個 reqId 并不相同。
String reqId = Integer.toString(42);
...
String reqId = Integer.toString(6*7);
一種成本高昂的簡單解決方案是:
synchronized(reqId.intern()) {
而是制作您cache的 aConcurrentHashMap或其 Navigable 版本的地圖。
Cache cache = new Cache();
class Cache {
ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
void add(String key, String value) {
map.merge(...);
}
}
public void saveInCatch(String reqId, String desc) {
cache.add(reqId, desc);
}
通過使用merge,您可以將更改保留在并發 put 中。
除了添加到地圖之外,如果還有一些狀態更改,您可以通過synchronized地圖的內部鍵來做到這一點。
請注意,我不喜歡這種解決方案,因為它很脆弱,而且很難測驗。
原子操作的并發佇列可能更可靠。我們現在可能只關注資料的一個方面。
uj5u.com熱心網友回復:
擁有一個鎖是一種選擇,但只允許 1 個并發請求。
另一種方法是使用條帶鎖。因此,您創建了一個陣列 objects,并根據字串的哈希值,確定條帶鎖中 object 的索引。
所以你會得到這樣的東西:
synchronized(stripedLock[toIndex(key)]){
...
}
uj5u.com熱心網友回復:
您正在查看一個執行緒何時寫入另一個執行緒應該能夠讀取它
我認為這樣做會很棘手。因為值應該被重繪 到記憶體中。
檢查類似問題的鏈接
Java執行緒:在同一個物件上同步讀取和寫入值
uj5u.com熱心網友回復:
同步塊適用于您匹配的物件的實體 ID
所以用你的代碼我可以創建兩個字串
String id1 = "1234";
String id2 = "1234";
如果兩個執行緒使用包含相同 ID 的不同變數呼叫您的函式,那么它們不會被阻塞,它們都會運行。
如果您真的想在這樣的字串上同步,您應該為每個可能的 Id 創建靜態最終 String 物件
注意:如果您決定使用像 Integer 這樣的數字型別來同步物件,那么您可能會發現它適用于低數字,然后隨著數字變大而停止作業,因為 Java 使用 Ints 表,因此只會創建 1 的一個版本對于您的代碼,但 9999 的每個實體都可能具有唯一的物件 ID
uj5u.com熱心網友回復:
如果您正確理解同步,它將鎖定方法和物件,直到它被一個執行緒使用完成,但您有時也希望reqId同時訪問。您可以volatile在變數中使用關鍵字private volatile String reqId;(reqId創建實體變數)。根據書:
volatile-該關鍵字向VM表明該值可能被外部執行緒更改并有效地自動同步對其的訪問。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/447441.html
