如何保护静态方法不被多个线程访问

How to protect static methods from access by several Threads

我有一个 class,只有我在代码中广泛使用的静态方法。它做一些事情(在这种情况下与文件和 URL 相关),例如:

public class FileUtilities {
  private FileUtilities () {
  }

  public static File getDirectory(String path) {
    if (path == null) {
      return null;
    } else {
       File file = new File(path);
       if (file.exists()) {
          if (file.isDirectory()) {
             return file;
          } else {
             return file.getParentFile();
          }
       } else {
          return null;
       }
    }
 }

}

当然这个方法只是一个例子,但是很多方法都是不可重入的。现在我想更改我的程序以使用并行线程来提高性能,但当然使用这种代码它不会工作(例如使用 ForkJoinPool 时)。

重构我的代码的最佳方法是什么(知道我希望尽可能保留静态方法,或者至少使用 Singleton 模式,因为我在我的很多地方都使用这个 class代码,它们只是实用方法,没有副作用。

我知道我可以同步所有静态方法,例如:

public synchronized static File getDirectory(String path) {

但我怀疑在某些情况下这会导致死锁。

我考虑过使用 ThreadLocal,但我不确定如何在我的情况下使用它。我的一个想法是:

public class FileUtilities {
  private static final ThreadLocal<FileUtilities> tlocal = ThreadLocal.withInitial(() -> new FileUtilities());
  private FileUtilities () {
  }

  public static File getDirectory(String path) {
     return tlocal.get().getDirectoryImpl(path);
  }

  private File getDirectoryImpl(String path) {
    if (path == null) {
      return null;
    } else {
       File file = new File(path);
       if (file.exists()) {
          if (file.isDirectory()) {
             return file;
          } else {
             return file.getParentFile();
          }
       } else {
          return null;
       }
    }
 }

}

它是正确的,还是无可救药的乱码?

Is it correct, or is it hopelessly broken code?

我讨厌这样说...但是后者。


一般来说,您不能仅采用单线程代码库并通过随机 使事情同步来使其成为多线程。这会导致以下问题:

  • 太多不必要的锁定导致并发瓶颈
  • 死锁。

您不能随机使用线程局部变量来避免锁定事物的需要。

您需要做的是彻底了解您的(提议的)多线程设计以及共享状态的位置。 (理想情况下,你将共享状态保持在最低限度 and/or 使在共享状态上运行的“动词”快速。)只有当你了解所有这些后,你才能决定需要使用什么 synchronizedvolatile、原子类型等等。

死锁总是需要考虑的问题。但它们不一定是一个问题。您需要做的是了解导致它们的模式以及如何避免这种情况。

(简而言之,它只发生在两个(或更多)线程同时锁定两个(或更多)共享对象时。T1 持有 A 并想要 B。T2 持有 B 并想要 A。死锁。简单的解决方案是他们应该以相同的顺序获取锁;例如先A然后B。这样他们就不会死锁。)


现在了解您示例的细节。

  • getDirectory() 未在 JVM 中的任何共享状态上运行。所以写的是线程安全的,不需要同步

  • 至于使用本地线程的FileUtilities版本,您似乎在跳过一个箍,为每个线程创建一个单独的FileUtilities实例.. . 这样你就可以从你的 static 方法调用它的方法......并避免让它同步(因为它会被线程限制)。但是 FileUtilities class 是无状态的,正如我所说 getDirectory() 已经是线程安全的。

即使您确实将 getDirectory() 设为 synchonized 方法(不必要的!!)...该方法仍然不会有死锁的风险...因为它不会在方法主体中获取任何 other 锁。不会出现死锁情况。


但假设 getDirectory() 确实需要同步,并且它确实尝试获取其他锁。 (因此潜在的僵局是一个真正的问题。)

线程本地是正确的解决方案吗?

可能不会。更好的选择是创建 class 的“丢弃”实例,使用它,然后丢弃。 Java 的设计和实现使得创建和丢弃轻量级对象相对高效。

我的建议是,当您有明确的证据 性能问题时,只求助于您建议的“优化”。不要做过早的优化事情。


It appeared that my Thread exception was upstream in my code, not in this utility class, but it created exception in it because what was sent to the utility class was "garbage".

我不太确定你在这里说的是什么。

不过,这里也有一个教训。在尝试修复错误之前正确诊断错误很重要。在调查其他(更简单的)解释之前,不要只是假设您有线程问题。而且即使你已经排除了其他的可能性,你仍然需要了解导致线程问题的机制才能修复它。

似乎我的线程异常在我的代码的上游,而不是在这个实用程序 class 中,但它在其中创建了异常,因为发送到实用程序 class 的是“垃圾