【问题标题】:How to protect static methods from access by several Threads如何保护静态方法不被多个线程访问
【发布时间】:2021-09-28 08:43:03
【问题描述】:

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

}

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

【问题讨论】:

  • 你的getDirectory() 是线程安全的,所以根本不应该碰它。应检查其他方法,并可能提出synchronized。你怎么怀疑你会陷入僵局?除了类级别的锁,您还有其他锁吗?如果在那里更有意义,您还可以在更高级别上处理并发。如果您必须询问您的ThreadLocal 设计,请不要这样做。我认为您正在尝试用代码复杂性来换取假定的性能。性能需要衡量,并发代码需要仔细设计。
  • 仅供参考,没有任何直接的理由来保护 方法 免受并发访问。方法访问的数据总是需要保护。如果您认为某些方法应该在访问某个变量或数据结构或对象之前锁定一个锁,那么 每个 访问同一变量/结构/对象的方法都应该锁定同一个锁。

标签: java multithreading thread-safety thread-local


【解决方案1】:

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

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


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

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

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

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

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

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


现在是你的例子的细节。

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

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

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


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

线程局部是正确的解决方案吗?

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

我的建议是,只有当您有明确的证据存在性能问题时,才诉诸“优化”,例如您提出的优化。不要做过早的优化。


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

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

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

【讨论】:

  • 您好,在多线程上下文中使用此类时出现异常,而在单个线程上下文中没有。我假设它来自这个类,它在我的代码的很多地方都被大量使用,但它可能来自其他地方。我会检查的。
  • 我在该类中看不到任何会导致异常的东西。请提供一个示例堆栈跟踪...
  • 我的线程异常似乎在我的代码的上游,而不是在这个实用程序类中,但它在其中创建了异常,因为发送到实用程序类的是“垃圾”
【解决方案2】:

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

【讨论】:

  • 这不是一个正确的答案,因为它甚至不会尝试回答您提出的问题。它只是说您提出原始问题的动机是错误的。但这并不能回答您提出的(真正的)问题……其他读者可能想知道答案。 (IMO,这应该只是对您的问题的评论。)
  • 是的,但是如何在不说明已回答的情况下结束问题?我不是在那里发表评论,只是问一个问题。
  • 您无需关闭您的问题。此外,“接受”不会关闭问题。其他人仍然可以回答。如果你想“撤回”一个问题,恐怕为时已晚。还有其他赞成的答案,这会阻止您删除您的问题。 (这是为了保留其他人在回答问题上所付出的努力。坦率地说,如果人们可以随心所欲地删除他们的问题,我不会花时间写答案。)
猜你喜欢
  • 1970-01-01
  • 2014-12-19
  • 2011-01-24
  • 2012-01-25
  • 2012-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多