保持线程安全,同时防止可能的同步回调死锁

Posted

技术标签:

【中文标题】保持线程安全,同时防止可能的同步回调死锁【英文标题】:Maintain thread safety while preventing deadlock from possibly synchronous callback 【发布时间】:2018-01-05 01:56:01 【问题描述】:

我有一个类似下面的 API,其中 Baz 是工作人员实现。 这个Bar 需要是线程安全的,这在与 Baz 的回调交互时会变得很棘手。

需要在回调中引用当前的 baz 实例(可以在工作线程上调用或同步调用)。 cmets 应该显示问题:

final class Bar 
  final Lock lock = new ReentrantLock();
  Baz baz; // Guarded by lock.

  void run()  // Called by any thread.
    lock.lock();
    if (baz.isRunning()) 
      lock.unlock();
      return;
    
    baz = new Baz();
    // If it unlocks here, the next line may execute on the wrong Baz.
    // If it doesn't unlock here, there will be a deadlock when done() is called synchronously.
    // lock.unlock();
    baz.run(new Baz.Callback()  // May be called synchronously or by Baz worker thread.
      @Override
      public void done() 
        lock.lock();
        baz = new Baz();
        lock.unlock();
      
    );
  

有没有一种好方法可以使这项工作正常工作,同时又不会导致死锁?

编辑:更简洁:

final class Foo 
  final Lock lock = new ReentrantLock();

  void run() 
    lock.lock();
    worker.enqueue(new Callback() 
      @Override void complete() 
        lock.lock(); // Could cause deadlock.
      
    );
    lock.unlock();
  

【问题讨论】:

【参考方案1】:

不确定是否完全得到您想要实现的目标,但也许这就是您想要的?

final class Bar 
    final Lock lock = new ReentrantLock();
    Baz baz = new Baz();

    void run() 
        if (!lock.tryLock()) 
            return;
        
        try 
            CountdownLatch callbackFlag = new CountdownLatch(1);
            baz.run(new Baz.Callback() 
                @Override
                public void done() 
                    callbackFlag.countDown();
                
            );
            try 
                callbackFlag.await(); // better use overloaded method with max timeout waiting. you don't probably want to wait forever
                baz = new Baz(); // do you really want to reinit Baz on each execution?
             catch (InterruptedException e) 
                // decide what you want to happen here
            
         finally 
            lock.unlock();
        
    

【讨论】:

不过,baz 分配需要在回调中。 为什么要在回调中引用?我发布的代码确保在回调完成之前 baz 不会重新初始化 无论如何,如果你真的需要,你可以把那行代码移到回调中;无论如何,一切都会好起来的,线程安全也会好的。

以上是关于保持线程安全,同时防止可能的同步回调死锁的主要内容,如果未能解决你的问题,请参考以下文章

线程同步与死锁

Java_多线程实现同步

软件构造 并发3(线程安全性)----锁定和同步

同步函数死锁现象

Java面试宝典线程安全问题|线程死锁的出现|线程安全的集合类

进程死锁