代码之家  ›  专栏  ›  技术社区  ›  uckelman

从不拥有锁的线程中解锁锁,或者重新设计以避免这种情况发生?

  •  3
  • uckelman  · 技术社区  · 15 年前

    我有一个归档对象,它管理各种字节数组和分发 InputStream S和 OutputStream 用于阅读和书写。每个字节数组都有一个关联的 ReentrantReadWriteLock .

    子类的构造函数 输入流 存档生成的文件获取相关数据的锁,而 close() 释放锁。现在,问题是:

    假设我有一个任务将运行在另一个需要从归档文件输入的线程上,那么 输入流 作为开始前的参数,并负责在流结束时关闭流。这些要求似乎与我的数据存档使用的锁定机制不兼容,因为

    1. 这个 输入流 在与关闭线程不同的线程上创建。
    2. 重入读写锁 必须由拥有它的线程释放。

    重写程序中需要 输入流 作为输入,为了避免1是不方便的,并使这些部件不那么灵活。使用一种允许改变所有权的锁,可以让我避免“2”,但在Java API中没有任何锁可以处理这个问题,而且如果我不需要的话,我不太喜欢敲一个自定义锁。

    这个问题有什么解决办法?

    5 回复  |  直到 15 年前
        1
  •  4
  •   Yuval Adam    15 年前

    最干净的方法是为客户提供除 InputStream ,即使这意味着重构稍微多一点。在您当前的设计中,您不能指望您的客户机释放锁,如果其中一个不释放呢?这不是很好的封装。

    让存档对象管理锁,并为客户机提供一个简单的API来获取数据 . 即使这意味着更改一些API。

    不同线程锁定和释放同一对象的问题是一个明显的迹象,表明您在当前的设计中遇到了一些轻微的错误。在前面提到的重构中工作,它将为您做更多的工作。

        2
  •  2
  •   Kevin Brock    15 年前

    根据你对我问题的回答。您可以简单地推迟 InputStream 通过一个用于制造 输入流 ThreadExecutorService . 然后服务中的线程请求 输入流 从这个对象开始构造它(因为它不存在),然后就设置好了。

    例如:

    public interface InputStreamMaker {
        public InputStream getOrCreateInputStream();
    }
    

    然后在你的主要课程中开始一切:

    static class InputStreamMakerImpl implements InputStreamMaker {
        private final Manager manager; // Your manager class (or use a normal Inner class on manager)
        private InputStream is;
    
        // other variables needed to define how to create input stream for the particular task here
    
        InputStreamMakerImpl(Manager manager) {
            this.manager = manager;
        }
    
        public InputStream getOrCreateInputStream() {
             if (is == null) {
                 // pass this object - so manager will have all the details to create the right stream
                 is = manager.createNewStream(this);
             }
    
             return is;
        }
    }
    

    然后在主线上…

    ...some method...
    InputStreamMakerImpl maker = new InputStreamMakerImpl(manager /* or this */);
    // set values needed in maker for your manager class to create the right input stream
    // start the worker and pass the InputStreamMaker (maker) as the parameter instead of the InputStream
    

    这个内部类可以根据需要有额外的信息来为工作者创建正确的流。现在 输入流 在右线程上创建/打开,该线程也可以关闭它并解锁锁。

    编辑 :在重读您的问题时,我看到您可能不想更改已经接受 输入流 . 然后,您仍然可以通过以下方式完成此操作:

    1. 创建的自定义子类 输入流 它包含了我上面定义的所有值 InputStreamMakerImpl .

    2. 习俗 输入流 具有内部方法 getInputStream() 这就是我定义的 getOrCreateInputStream() 上面。

    3. 那么所有的方法 输入流 只需委派呼叫,例如:

      public int read() throws IOException {
          return getInputStream().read();
      }
      
        3
  •  1
  •   Gladwin Burboz    15 年前

    java.io.InputStream 共有九种方法。您已经覆盖了一个,即。 close() 方法。

    我建议您使用惰性锁,即不要积极地获取构造函数中的锁。为了这个 ReentrantReadWriteLock 在您的 InputStream 子类并具有一个名为 doAcquireLock() 试图获得锁。然后重写 输入流 这样,在所有重写方法中,首先调用 DoAcquireLock()。 然后将操作委托给super。

    例子:

    public int read() 
        doAcquireLock(); // This method get's the lock
        return super.read();
    }
    

    使用这个锁,线程将首先调用上述八个方法中的任何一个,并负责关闭。

    为了确保您不会在死锁的情况下结束,您需要注意的是确保客户端在结束时调用Close方法以释放锁。此外,在通过具有布尔指示器来知道流是否已关闭(过时)来调用Close方法之后,不应调用任何方法。

        4
  •  0
  •   Gladwin Burboz    15 年前

    解决方案2:

    如果在线程结束前不需要一直保持锁定状态,则可以实现自定义 InputStream 下面你可以给你的客户。现在,当任何线程需要读取时,它将获取锁,然后读取,最后在读取完成后自动放弃锁。你习俗 OutputStream 应该类似地用写锁实现。

    .

    public class LockingInputStream extends InputStream {
        private final InputStream byteArrayInputStream;
        private final Lock r;
    
        public LockingInputStream(
                        InputStream byteArrayInputStream, 
                        ReentrantReadWriteLock rwl) {
            this.byteArrayInputStream = byteArrayInputStream;
            this.r = rwl.readLock();
        }
    
        @Override
        public int read() throws IOException {
            r.lock();
            try {
                return byteArrayInputStream.read();
            } finally { r.unlock(); }
        }
        @Override
        public int available() throws IOException {
            r.lock();
            try {
                return byteArrayInputStream.available();
            } finally { r.unlock(); }
        }
        ....
        @Override
        public void close() throws IOException {
            r.lock();
            try {
                byteArrayInputStream.close();
            } finally { r.unlock(); }
        }
    }
    
        5
  •  0
  •   uckelman    15 年前

    另一个解决方案是,我在仔细考虑这个问题时偶然发现的:查看问题的一种方法是,它是由锁的重入引起的。 ReentrantReadWriteLock 跟踪锁的所有权以处理重入。如果我想获取流创建时的锁,并释放流关闭时的锁,并关闭线程(而不是创建它们的线程)上的流,那么可重入锁将不起作用,因为错误的线程将拥有流锁。

    解决这个问题的一种方法是使用计数锁,但将所有锁都向外强制。首先,计数锁:状态是整数-1,0,1,…,其中-1表示写入,0表示未锁定,正n表示并发读取数。这样,写入仍然是独占的,但锁不是线程特定的。强制所有锁向外:我们有如下方法 exists() 我们需要获取一个读锁,但是 存在() 由某些已具有写锁的方法调用。所以,我们把 存在() 进入受保护的 existsImpl() ,仅当锁已被持有(即内部由其他人持有)时才应调用 *Impl() 方法。这样,一旦获得了一个锁,就只调用非锁定的impl方法,并且我们避免了重入的需要。

    这一切都很好,除了它最终变得极其复杂,以确保您不会在实现它时搞砸了调用一个公共锁方法(当您应该调用一个受保护的非锁方法时调用一个公共锁方法),从而导致死锁。其次,将所有锁都强制放在外面意味着您要保持锁的时间更长,因此这(可能)不利于并发性。

    所以,这是我尝试过的,但决定不使用。