多线程环境下的 mkdirs() 函数

mkdirs() function in multithreaded environment

我正在创建文件和文件夹树。我正在重写多线程。我看到的唯一弱点是创建文件夹时。现在它一个接一个(深入)。在我写下文件之前,我检查路径是否存在。如果没有,我使用 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 中执行此操作,但无论如何确定这是必要的并且不会减慢进程太多。

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

感谢您的建议。

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

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

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

正如 EJP 指出的 return of 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();
   }
}

我在这里写了一个新方法,return仅当目录之后不存在时才为 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 + " !");
        }
    }
};

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

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

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

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

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

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

我想采用一种天真的方法并声明一个两个线程都可以访问的单个 'global' 变量很容易,例如布尔 b ,然后在关键部分周围添加以下代码,

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

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

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