多条件vs多锁

pvabu6sv  于 2021-07-13  发布在  Java
关注(0)|答案(2)|浏览(492)

对于特定的线程安全数据结构,我需要保护对中心数据结构(即字节数组)的访问。在本例中,我选择使用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 . 此外,为了简单起见,代码将我更复杂的多条件等待和信号简化为单个条件。
使用这个,我不禁觉得代码似乎不必要的复杂和晦涩,不仅有两个锁嵌套,他们共享一个尝试最后,更不用说如何 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();
    }
}

经过一番检查,我无法决定前者是更好的主意还是后者(特别是在包含随机布尔值的情况下)。简化代码的想法似乎导致了更长的代码,在这种情况下非常违反直觉。
是否有一些惯例或令人信服的理由来论证:
每个锁定上下文使用一个锁(前一个代码,其中不同的锁定原因共享不同的锁)。
每个锁资源使用一个锁(后一个代码,其中要保护的中心结构使用一个锁,其他所有内容都作为访问所述结构的条件实现)。

9cbw7uwe

9cbw7uwe1#

后一个代码与前一个不同,只是通过使用foocondition手动实现lock lockfoo(对于与条相关的部分也是如此)。
由于这种锁的实现考虑到了foo临界区和中心临界区几乎相同,所以保证了在没有争用的情况下更快 foo() (在这种情况下,从不执行等待条件)。
除了性能方面的原因之外,前面的代码更可取,因为它是自文档的。此外,它还可以扩展到需要在没有lockcentral的情况下访问受lockfoo保护的数据的情况。在这种情况下,手动实现锁将失去其性能增益。

vnzz0bqm

vnzz0bqm2#

在您最初的示例中 lockFoo 以及 lockBar 锁是完全冗余的,因为 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();
    }
}

相关问题