簡體   English   中英

如何確保多個番石榴緩存不會互相鎖定?

[英]How to assure multiple guava caches do not lock each other?

據我了解,默認情況下,番石榴的緩存鎖定在一個鍵上。 因此,如果線程t1和線程t2都嘗試獲取相同的密鑰,則只有一個線程實際上會加載它,而另一個線程等待第一個線程獲取值,然后再獲取相同的密鑰。

如果您要處理相互依賴的多個緩存,這是一個很好的默認行為,但並不是最佳選擇。

我們處於一種情況,其中我們有多個緩存實例和多個線程。 線程查詢多個緩存以完成其工作。 因此,緩存實例相互依賴。 實際上,可以歸結為以下情況:

線程t1

Value v1 = cache1.get(k, new Callable<Value>() {
    Value call() {
        //do something
        Value v2 = cache2.get(k, doRealWorkCallable());
        Value v = calculateFrom(v2)
        return v;
    }
});

線程t2

Value v2 = cache2.get(k, new Callable<Value>() {
    Value call() {
        //do something
        Value v1 = cache1.get(k, doRealWorkCallable());
        Value v = calculateFrom(v1)
        return v;
    }
});

如果我正確理解了鎖定策略,則上述情況可能會導致死鎖:線程t1在cache1中持有k的鎖,在cache2中等待k的鎖。 線程t2為cache2中的k持有鎖,等待cache1中的k為鎖。

番石榴有什么辦法可以防止這種僵局? 如我所見,只要您使用CacheLoaderCallable ,您就可能陷入僵局,因為兩者都鎖定了它們正在加載的密鑰。

我認為我們可以使用舊的“檢查是否存在於緩存中,如果不存在:計算並放置在緩存中”:

Value v1 = cache1.getIfPresent(k);
if (v1 == null) {
    //get it using cache2
    Value v2 = cache2.getIfPresent(k);
    if (v2 == null) {
        v2 = doRealWork();
        cache2.put(k,v2);
    }
    v1 = calculateFrom(v2);
    cache1.put(v1);
 }

(當然,第二個線程也可以相反)

這帶來了“可能不需要”的值計算成本,而不會冒死鎖線程的風險。

番石榴有什么更好的方法嗎?

編輯:下面的具體示例

我們正在從無法控制的外部系統調用多個Web服務。 這些Web服務傳遞的數據是分層的,並通過引用鏈接。 例:

class WSOrganization {
    Integer id;
    String name;
    List<Integer> employeeIds; //like a collection of foreign keys
}
class WSEmployee {
    Integer id;
    String name;
    Integer organizationId; //like a foreignkey
}

在某些地方我們需要員工,在某些地方我們需要組織。 如果我們要求組織,我們會很熱心。 如果我們拿到員工,我們也需要組織。 該代碼分布在多個服務存根之間,依此類推,但最終歸結為:

//in EJB 1
PrefetchedOrganization getOrganization(Integer orgId) {
    WSOrganization org = orgService.getOrganizationById(orgId);
    for (Integer employeeId : org.employeeIds) {
        WSEmployee employee = employeeService.getEmployeeById(employeeId);
        listOfEmployees.add(employee);
    }
    return createPrefetchedOrgWithEmployees(org, listOfEmployees);
}

//in EJB 2
PrefetchedEmployee getEmployee(Integer employeeId) {
    WSEmployee employee = employeeService.getEmployeeById(employeeId);
    PrefetchedOrganization orgOfEmployee = ejb2.getOrganization(employee.organisationId);
    return orgOfEmployee.employee(employeeId);
}

現在,我們想通過在EJB 1和EJB 2上使用javax.interceptor.Interceptor引入緩存。

@AroundInvoke
public Object aroundInvoke(InvocationContext invocation) {
    Object object = getElementFromCache(invocation);
    return object;
}

可能會發生兩個線程以相反的順序調用這兩個方法的情況,我們絕對不希望它們彼此阻塞。

EDIT2:使用Hashmaps的getElementFromCache()的示例實現

Integer id = idFrom(invocation);
if (cache.containsKey(id)) {
    return cache.get(id);
} else {
    Object result = invocation.proceed();
    cache.put(id, result);
    return result;
}

執行緩存的Callable時,番石榴似乎沒有鎖定鍵。 否則,您提供的代碼將始終死鎖,例如:

getOrganization(1337):
    (contains employee X)
    getEmployee(x):
        getOrganization(1337) // deadlock by recursion!!!

Cache.get文檔指出:

此方法為常規的“如果已緩存,則返回;否則創建,緩存並返回”模式提供了簡單的替代方法。

請注意,我還沒有嘗試過,但是只是從文檔Guava看來,多次運行Callable會出錯。

簡而言之,這不是問題!

暫無
暫無

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

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