【问题标题】:mkdirs() function in multithreaded environment多线程环境中的 mkdirs() 函数
【发布时间】:2015-06-11 20:55:00
【问题描述】:

我正在创建文件和文件夹树。我正在重写多线程。我看到的唯一弱点是创建文件夹时。现在它一一进行(深入)。在我写下文件之前,我检查路径是否存在。如果没有,我会使用 mkdirs 来创建所有缺失的内容。

public void checkDir(String relativePath) {
        File file = new File(homePath + relativePath);
        if (!file.exists()) {
            if (file.mkdirs()) {
                log.info("Directory: " + homePath + relativePath + " is created!");
            } else {
                log.error("Failed to create directory: " + homePath + relativePath + " !");
            }
        }
    }

我有一个问题,当我使用两个线程时会发生什么。一个有路径 A/B/C,另一个有 A/B/D。假设我只有 A 文件夹,但没有 B。所以他们俩都会检查该路径是否不存在并想要创建它。所以其中一个可能会失败,因为另一个会更快。那么我该如何管理呢?

  1. 我正在考虑删除存在条件并使其失败,但我无法捕获任何 AlreadyExists 异常..
  2. 先创建目录树(但我认为会有更好的方法?)
  3. 将目录创建作为关键部分并使其按顺序进行 - 不知道如何在春季执行此操作,但无论如何热确定它是必要的并且不会过多地减慢进程。

也许是我想太多了,但理论上这种情况可能会发生。目前我使用常规线程,但我想为此使用 spring TaskExecutor。它自己处理关键部分,但这不是共享变量或任何东西,而且路径不同,所以我认为它不会识别它。

感谢您的建议。

【问题讨论】:

  • 如果更快,我不会失败 java 会意识到该目录存在并且不会成功。
  • 既然这看起来对性能不重要,为什么不直接同步方法呢?
  • 您应该考虑 Kári 的回答。原子不会自动暗示操作是同步的。
  • 请注意,连接字符串以创建文件路径容易出错且脆弱。好像代表文件首先是字符串。我建议您从一开始就重写代码以使用Path,并在Path 上使用relativizeresolve 等方法来处理这些路径。最后,使用Files 进行操作,因为它的错误处理要好得多。 TL;DR 不要使用String 来表示类型化的数据,也不要使用File API。

标签: java multithreading spring mkdirs


【解决方案1】:

File.mkdirs() 方法用于创建目录及其所有父目录(如果它们不存在)。 Ergo 调用exists(). 毫无意义,无论如何都会检查存在。致电exists() 只是在浪费时间。 mkdirs() 本质上是一个原子操作:试图超越它真的没有意义。

请注意,false 的返回值不一定是失败。它可能只是表明路径中的所有目录都已存在。

基本上你的问题的前提是错误的。

【讨论】:

  • 谢谢,这是非常简单的解决方案:)
  • 另见JDK-4742723(已修复),它描述了多线程目录创建失败。
【解决方案2】:

似乎没有一个答案解决 mkdirs() 是否是线程安全的问题,一个答案指出 mkdirs() 是原子的,但可能存在失败的情况。此函数本质上处理文件系统,因此它可能涉及对相应主机上的操作系统的系统调用,并且如果您还不知道应用程序将要使用的目标系统,那么确定这些系统调用是否实际上是线程安全的可能是不可能的上使用。

例如,即使 mkdirs() 在创建文件夹结构之前检查是否存在,在以下情况下会发生什么,

线程 1 调用 mkdirs(),它固有地检查文件夹结构的存在并确定它不存在。那时,线程 1 被抢占。

线程 2 调用 mkdirs(),它固有地检查文件夹结构的存在并确定它不存在,然后继续创建文件夹结构。

线程 1 再次启动并继续尝试创建文件夹结构,之前确定它之前不存在。

那里会发生什么?我不知道,这一系列事件很难测试,尤其是在知道创建文件夹系统调用因操作系统而异的情况下。线程安全和避免引入可能难以跟踪和调试的错误的最佳选择是在代码的这个关键部分实现一定程度的互斥。

我想采取一种简单的方法并声明两个线程都可以访问的单个“全局”变量会很容易,例如布尔值 b,然后在您的关键部分周围添加以下代码,

synchronized(b) {
     // Your critical section here
}

这将保证如果一个线程已锁定 b 它只能在另一个等待时访问临界区,从而确保 mkdir() 不会被两个线程调用。

但是,如果您想了解有关多线程以及如何在较低级别实现互斥的更多信息,在这种情况下,我建议您查看信号量以及如何实现它们来解决此问题。

【讨论】:

  • 我同意这个解释。原子不会自动暗示操作是同步的。
  • 您不知道“它是否天生检查存在”。如果我正在实施它,我不会检查任何东西。我只是尝试创建所有相关的目录,从父级到子级,每个目录在内核中都是原子的,然后当且仅当所有创建都成功时才返回 true。因此不会出现多线程问题。我认为太阳队的人也看到了这一点。
【解决方案3】:

正如 EJP 指出的那样,返回 false 可能意味着很多事情,有些错误,有些则不然。如果你想记录它实际上无法创建目录的事实,你应该在之后检查是否存在:

public final class DirectoryHelper {
   private DirectoryHelper(){}

   public static boolean createDirectories(File path) {
      if (path.mkdirs()) return true; //definitely has new dir
      // if false, just look afterwards for the existence of the directory
      // also opportunity to throw own exceptions if you prefer
      return path.exists() && path.isDirectory();
   }
}

我在这里编写了一个新方法,该方法仅在之后目录不存在时才返回 false。我不在乎它是刚刚制造出来还是已经存在。由于新订单,我也不需要synchronized 块。

您的代码如下所示:

public void checkDir(String relativePath) {
    File file = new File(new File(homePath), relativePath);
    if (!file.exists()) { // just to prevent logging of existing dirs
        if (DirectoryHelper.createDirectories(file)) {
            log.info("Directory: " + file + " is created!");
        } else {
            log.error("Failed to create directory: " + file + " !");
        }
    }
};

【讨论】:

  • 或者你可以使用Path API,它的指定要好得多。
  • 我的回答适用于哪一点?
  • 检查返回值然后尝试找出问题所在的逻辑。
  • 但这就是全部答案。也许发布你的意思作为答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-08
  • 2018-06-12
  • 2018-05-10
  • 2012-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多