Java 中的 RAII 设计模式

2022-09-01 23:08:23

来自C++背景,我是RAII模式的忠实粉丝。我已经广泛使用它来处理内存管理和锁定管理以及其他用例。

在Java 1.7中,我看到我可以使用try-with-resources模式来创建RAII模式。

我使用RAII创建了一个示例应用程序,它可以正常工作,但是我看到来自java的编译器警告。

示例应用程序

try(MyResource myVar = new MyResource(..))
{
    //I am not using myVar here 
}

我收到以下错误

warning: [try] auto-closeable resource node is never referenced in body of corresponding try statement

我理解警告,它暗示我应该在try块中使用变量,我并不需要一直这样做。

考虑到这一点,我假设Java并没有真正支持RAII,我可能滥用了仅用于资源管理的功能,而不是C++中的RAII等效物。

几个问题:

  1. 我的理解是否正确?
  2. 忽略这些警告的风险有多大?
  3. 如何通过蚂蚁忽略这些警告?
  4. 有没有一个简单的方法来克服这个问题?

对于4,我正在考虑将构造函数调用拆分为更简单的构造函数和这样的实例方法

try(MyResource myVar = new Resource())
{
   myvar.Initialize()
   ....

}

它解决了编译器问题,但从RAII设计中汲取了本质。


答案 1

扩展Radiodef的答案。我认为使用资源试用的RAII对于java来说是完全可以接受的模式。但实际上要抑制警告

  • 您需要使用 而不是 .@SuppressWarnings("try")@SuppressWarnings("unused")
  • 并在方法上添加注释而不是变量声明

应用了上述几点的示例:

   @SuppressWarnings("try")
   void myMethod1() {
       try(MyResource myVar = new MyResource(..)) {
           //I am not using myVar here 
       }
   }

扩展模式本身。我已经广泛使用它来管理读写锁,并且效果很好。

在我的代码中,我使用了这个技巧来先发制人地解锁一些资源以增加并发性,如下所示:

try (Guard g1 = new Guard(myLock1)) {
    someStuffThatRequiresOnlyLock1();
    try (Guard g2 = new Guard(myLock2)) {
        someStuffThatRequiresBothLocks();
        if (isSomething) {
            g1.close();
            someMoreSuffThatRequiresOnlyLock2()
        } else {
            someMoreSuffThatRequiresBothLocks();
        }
    }
}

锁始终以相同的顺序获取,但会根据需要执行解锁,为并发处理留出尽可能多的空间。使其与读写锁一起使用的调整是修改 Guard 类以允许重复关闭:

public class Guard implements AutoCloseable {
    private final Lock lock;
    private boolean isClosed = false;

    public Guard(Lock lock) {
        this.lock = lock;
        lock.lock();
    }

    @Override
    public void close() {
        if (!isClosed) {
           isClosed = true;
           lock.unlock();
        }
    }
}

更新:从 Java 9 开始,您无需在 Java 中禁止显示此模式的任何警告。您可以在 -子句中引用单个有效的最终变量,这将导致不发出警告:try

    Guard g1 = new Guard(myLock1);
    try (g1) {
        someStuffThatRequiresOnlyLock1();
        Guard g2 = new Guard(myLock2);
        try (g2) {
            someStuffThatRequiresBothLocks();
            if (isSomething) {
                g1.close();
                someMoreSuffThatRequiresOnlyLock2()
            } else {
                someMoreSuffThatRequiresBothLocks();
            }
        }
    }

答案 2

1. 我的理解是否正确?

或多或少。是的,您可以以这种方式使用试用资源,是的,它在语义上与RAII相当。不同之处在于没有破坏解除分配,只有方法调用。

查找仅为包装某些资源管理逻辑而编写的对象的情况并不常见,例如:

import java.util.concurrent.locks.Lock;

public class Guard implements AutoCloseable {
    private final Lock lock;

    public Guard(Lock lock) {
        this.lock = lock;
        lock.lock();
    }

    @Override
    public void close() {
        lock.unlock();
    }
}
try(Guard g = new Guard(myLock)) {
    // do stuff
}

如果你正在与其他程序员一起工作,你可能需要向一些人解释它意味着什么,但我个人认为如果它漂浮在你的船上,它不会有问题。

我不建议的是编写奇怪的代码,例如

try(AutoCloseable a = () -> lock.unlock()) {
    lock.lock();
    // do stuff
}

这肯定会在代码审查中生成WTF。

2. 忽略这些警告的风险有多大?

没有风险。警告实际上只是一个通知。你知道,万你不知道它。

要摆脱警告,您可以尝试:

try(@SuppressWarnings("unused")
    MyResource myVar = new MyResource())

或者还可以查看“你如何获得*蚂蚁*不打印出javac警告?

IDE 应提供全局禁止显示特定警告或仅禁止显示单个语句(不带注释)的选项。


推荐