多个条件与多个锁

时间:2015-04-28 09:41:57

标签: java multithreading concurrency locking reentrantlock

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

并发条件很复杂,如下所示:

  1. 中心字节数组必须独占保护(即一次一个线程)。
  2. 两个访问方法(foo和bar)必须能够并发运行(如果它们尝试访问中心字节数组,则在内部阻塞)。
  3. 对任何方法(foo和bar)的调用都需要是独占的(即从不同的线程多次调用foo会导致一个线程阻塞)。
  4. 在我最初的实现中,我选择实现两个嵌套锁,如下所示:

    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. 每个锁定资源使用一个锁(后一个代码,要保护的中心结构使用单个锁,其他所有内容都作为访问所述结构的条件实现)。

2 个答案:

答案 0 :(得分:5)

后者代码与前者的不同之处仅在于使用 fooCondition 手动实现锁 lockFoo (对于 bar - 相关的情况也是如此一部分)。

因为这样的锁实现被考虑在内, foo 关键部分与中心一部分几乎相同,所以在没有争用的情况下保证更快在foo()(等待 fooCondition 在这种情况下永远不会执行。)

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

答案 1 :(得分:0)

在您的原始示例中,lockFoolockBar锁是完全多余的,因为foo()bar()都不会在不锁定lockCentral锁定的情况下执行任何操作。除非您更改程序的设计,否则lockCentral是您需要的唯一锁定。

你说你认为你的第一个例子“太复杂了”,但你的第二个例子是更多复杂的方式。您好像只​​是想用自己设计的锁定代码替换lockFoolockBar。但那有什么意义呢?它不会与你的第一个例子不同的东西。

锁定的目的是什么?你说“对任何方法(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();
    }
}