首页 > 解决方案 > 如何保护静态方法不被多个线程访问

问题描述

我有一个只有静态方法的类,我在代码中广泛使用了这些方法。它做一些事情(在这种情况下与文件和 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.

重构我的代码的最佳方法是什么(知道我希望尽可能保留静态方法,或者至少使用单例模式,因为我在代码中的很多地方都使用了这个类,而这些只是实用方法,无副作用)。

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

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;
       }
    }
 }

}

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

标签: javamultithreadingthread-safetythread-local

解决方案


它是正确的,还是无可救药地破坏了代码?

我讨厌这样说……但后者。


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

  • 过多不必要的锁定导致并发瓶颈
  • 僵局。

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

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

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

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


现在了解您的示例的详细信息。

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

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

即使您确实创建getDirectory()了一个synchonized方法(不必要地!!)......该方法仍然不会有死锁的风险......因为它不会在方法体中获取任何其他锁。不会出现死锁情况。


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

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

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

我的建议是,当您有明确的性能问题证据时,只诉诸“优化”,就像您提出的那样。不要做过早的优化事情。


看起来我的 Thread 异常在我的代码的上游,而不是在这个实用程序类中,但它在其中创建了异常,因为发送到实用程序类的内容是“垃圾”。

我不完全确定你在这里说什么。

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


推荐阅读