对于特定的线程安全数据结构,我需要保护对中央数据结构(即字节数组)的访问。在这种情况下,我选择使用 ReentrantLocks 是因为它的公平策略以及创建多个条件的高级功能。
并发的条件比较复杂,列举如下:
- 必须专门保护中央字节数组(即一次一个线程)。
- 两种访问方法(foo 和 bar)必须能够同时运行(如果它们试图访问中央字节数组,则会在内部阻塞)。
- 对任何方法(foo 和 bar)的调用必须是独占的(即从不同线程多次调用 foo 将导致一个线程阻塞)。
在我最初的实现中,我选择实现两个嵌套锁,如下所示:
ReentrantLock lockFoo = new ReentrantLock(true);
ReentrantLock lockCentral = new ReentrantLock(true);
Condition centralCondition = lockCentral.newCondition();
public void foo(){
// thread-safe processing code here
lockFoo.lock();
lockCentral.lock();
try{
// accessing code here
try{
// waits upon some condition for access
while(someCondition){
centralCondition.await();
}
}catch(InterruptedException ex){
// handling code here
}
// more processing
}finally{
lockCentral.unlock();
lockFoo.unlock();
}
}
该结构在方法上是等价的bar
,只是带有另一个锁对象lockBar
。此外,为简单起见,该代码已将我更复杂的多条件等待和信号减少为单个条件。
使用它,我不禁觉得代码似乎不必要地复杂和晦涩,因为不仅嵌套了两个锁,它们共享一个 try-finally,更不用说如何在整个过程中lockCentral
被释放和重新获取多次lockFoo
.
相反,我尝试重新组织外部锁(lockFoo
和lockBar
)作为lockCentral
代替的条件,如下所示:
ReentrantLock lockCentral = new ReentrantLock(true);
Condition fooCondition = lockCentral.newCondition();
Condition centralCondition = lockCentral.newCondition();
boolean isInFoo = false;
public void foo(){
// thread-safe processing code here
lockCentral.lock();
try{
// implement method exclusiveness via fooCondition
try{
while(isInFoo){
fooCondition.await();
}
isInFoo = true;
}catch(InterruptedException ex){
return;
}
// accessing code here
try{
// waits upon some condition for access
while(someCondition){
centralCondition.await();
}
}catch(InterruptedException ex){
// handling code here
}
// more processing
}finally{
isInFoo = false;
fooCondition.signal();
lockCentral.unlock();
}
}
经过一番检查,我无法决定前者是更好的主意还是后者(尤其是包含该随机布尔值)。简化代码的想法似乎导致代码更长,在这种情况下非常违反直觉。
是否有一些约定或令人信服的理由来论证:
每个锁定上下文使用一个锁(前一个代码,锁定的不同原因共享不同的锁)。
每个锁定资源使用一个锁(后一种代码,要保护的中央结构使用单个锁,其他所有内容都作为访问所述结构的条件实现)。