4

对于特定的线程安全数据结构,我需要保护对中央数据结构(即字节数组)的访问。在这种情况下,我选择使用 ReentrantLocks 是因为它的公平策略以及创建多个条件的高级功能。

并发的条件比较复杂,列举如下:

  1. 必须专门保护中央字节数组(即一次一个线程)。
  2. 两种访问方法(foo 和 bar)必须能够同时运行(如果它们试图访问中央字节数组,则会在内部阻塞)。
  3. 对任何方法(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.

相反,我尝试重新组织外部锁(lockFoolockBar)作为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();
    }
}

经过一番检查,我无法决定前者是更好的主意还是后者(尤其是包含该随机布尔值)。简化代码的想法似乎导致代码更长,在这种情况下非常违反直觉。

是否有一些约定或令人信服的理由来论证:

  1. 每个锁定上下文使用一个锁(前一个代码,锁定的不同原因共享不同的锁)。

  2. 每个锁定资源使用一个锁(后一种代码,要保护的中央结构使用单个锁,其他所有内容都作为访问所述结构的条件实现)。

4

2 回答 2

5

后一个代码与前一个代码的不同之处在于使用 fooCondition 手动实现 lock lockFoo bar相关的部分也是如此)。

因为这种锁实现考虑到了foo临界区几乎与中心区相同,所以保证在没有争用foo()的情况下更快(在这种情况下永远不会执行等待fooCondition)。

除了性能方面的原因,前一种代码更可取,因为它是自记录的。此外,它可以扩展到需要在没有lockCentral的情况下访问受lockFoo保护的数据的情况。在这种情况下,手动实现锁会失去其性能增益。

于 2015-04-28T13:19:17.153 回答
0

在您的原始示例中,lockFooandlockBar锁是完全多余的,因为如果不锁定锁,既不foo()也不bar()能做任何工作lockCentral。除非您更改程序的设计,否则您lockCentral需要的唯一锁。


您说您认为您的第一个示例“太复杂”,但您的第二个示例复杂得多。看起来你只是想用你自己设计的锁定代码来替换lockFoo和替换。lockBar但那有什么意义呢?它不会任何与你的第一个例子不同的事情。


无论如何,锁定的目的是什么?您说“对任何方法(foo 和 bar)的调用都必须是独占的”。这是错误的开始:不要使用锁来保护方法;使用锁来保护数据

这个“中心字节数组”是什么?线程对它做了什么? 为什么需要保护它?

foo() 操作的数据是什么?为什么需要保护它?bar() 操作的数据是什么?为什么需要保护它?

最重要的是,foo() 数据和 bar() 数据是否都需要与中央字节数组同时受到保护?

在一个设计良好的程序中,线程应该在不持有任何锁的情况下完成大部分工作:

SomeObject someObject;
SomeOtherObject someOtherObject;
boolean success = false;
while (! success) {

    someLock.lock();
    try {
        someObject = getLocalCopyOfSomeData();
        someOtherObject = getLocalCopyOfSomeOtherData();
    } finally {
        someLock.unlock();
    }

    doTheRealWork(someObject, someOtherObject);

    someLock.lock();
    try {
        success = updateCentralCopyOf(someObject) || updateCentralCopyOf(someOtherObject);
    } finally {
        someLock.unlock();
    }
}
于 2015-04-28T14:06:37.767 回答