【问题标题】:How to use mkdirs in a thread safe manner in Java?如何在 Java 中以线程安全的方式使用 mkdirs?
【发布时间】:2011-07-08 13:17:11
【问题描述】:

在遇到 mkdirs() 的问题并浏览互联网后,我觉得 mkdirs() 存在线程安全问题。

当多个线程可能试图创建相似的文件结构时,是否有办法确保正确创建目录?

谢谢

(在我的情况下,我将在 Android 上使用它)

【问题讨论】:

  • 您能否提供参考资料说明您为什么认为 mkdirs() 不是线程安全的?
  • 是否有任何已知的细节(对于 J2SE 和 Android)在哪些情况下这个问题很可能会出现? CPU 核心数、线程数、操作系统数、文件系统数?我正在尝试提出这个问题以进行测试。
  • @TedHopp 链接已失效?你能重新链接或解释一下是什么吗?
  • @cooton 我同意mkdirs() 似乎不是同步的!
  • @TedHopp 当多个线程正在运行并想要创建一个目录可以说/data/abc。第一个线程现在来执行方法 mkdir(),而 mkdir() 正在进行中,第二个线程来检查 dir 是否已经存在并知道它不存在并执行 mkdir()。

标签: java android io thread-safety mkdir


【解决方案1】:

我不确定 Android 是否支持并发包,但这是我的看法:

private static Lock fsLock = new ReentrantLock();

private void mkdir( File dir ) throws FileNotFoundException {

    if( dir.exists() ) {
        return;
    }

    fsLock.lock();
    try {
        if( !dir.exists() ) {
            log.info( "Creating directory {}", dir.getAbsolutePath() );
            if( !dir.mkdirs() ) {
                throw new FileNotFoundException( "Can't create directory " + dir.getAbsolutePath() );
            }
        }
    } finally {
        fsLock.unlock();
    }
}

如果目录已经存在,该方法会提前返回。如果它不存在,只有一个线程会尝试创建它。

【讨论】:

  • 这很棒。这解决了我在尝试创建相同目录的多个线程时遇到的问题。不是在 Android 上,而是在常规 Java 5 应用程序中。
  • 是的,这比看起来要难。
【解决方案2】:

在序列化所有内容的工作线程中创建所有目录。您可以使用 Looper 和 Handler 轻松将调用 mkdirs 的 Runnables 发布到您的工作线程。完成创建目录后,您可以调用 Looper.quit() 以在线程处理完最后发布的Runnable 后结束线程。 documentation for Looper 的示例代码显示了这样做是多么的微不足道。

【讨论】:

    【解决方案3】:

    一种可能的解决方案是 MkDirService(如下图所示),它只保证一个实例并在它自己的线程中运行。使用 BlockingQueue。

    首先是服务:

    package mkdir;
    
    import java.io.File;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    public class MkDirService extends Thread {
    
        private static MkDirService service;
        private BlockingQueue<File> pendingDirs = new LinkedBlockingQueue<File>();
        private boolean run = true;
    
        private MkDirService() {
        }
    
        public synchronized static MkDirService getService() {
            if (service == null) {
                service = new MkDirService();
                new Thread(service).start();
            }
            return service;
        }
    
        public void makeDir(File dir) {
            pendingDirs.add(dir);
        }
    
        public void shutdown() {
            run = false;
        }
    
        @Override
        public void run() {
            while (run || !pendingDirs.isEmpty()) {
                File curDir = null;
                try {
                    curDir = pendingDirs.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (curDir != null && !curDir.exists()) {
                    curDir.mkdir();
                    System.out.println("Made: " + curDir.getAbsolutePath());
                }
            }
        }
    }
    

    测试:

    package mkdir;
    
    import java.io.File;
    
    public class MkDirServiceTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            MkDirService mdServ = MkDirService.getService();
            mdServ.makeDir(new File("test1"));
            mdServ.makeDir(new File("test1/test2"));
            mdServ.makeDir(new File("test1/test3"));
            mdServ.shutdown();
    
        }
    }
    

    【讨论】:

    • 欢迎对我的示例进行任何更正,我确信有更好的方法可以做到这一点,并且认为这是一个很好的学习机会。
    【解决方案4】:

    好的,我知道这已经有一段时间不活跃了,但我想也许有一个简单的解决方案。您在该问题的 cmets 中链接的文章似乎表明唯一的问题是目录 not 正在创建。解决方案是这样做:

    if (!f.mkdirs()) {
        f.mkdirs();
    }
    

    但是,这似乎效率低下并且仍然存在问题。那么,为什么不简单地这样做呢:

    while (!f.mkdirs()) {}
    

    简单,但有效。

    编辑:经过一番思考,该示例可能会被遗忘并可能导致线程锁定。所以,这可能是一个更好的主意:

    while (!f.mkdirs()) { Thread.yield(); }
    

    当然,只有在您处于可能导致线程锁定的线程中并且不是高优先级的情况下,才建议这样做。只是把它放在那里。

    【讨论】:

    • ...真的,真的希望这永远不会在 mkdirs() 因真正原因(例如权限问题或格式错误的目录名称)失败的情况下运行。
    • @smackfu 好点。也许最好尝试一定次数,然后显示错误消息。
    • 我遇到了确切的问题,不幸的是,上述方法对我没有任何帮助,但这有效while(!dir.mkdirs()) { if(dir.isDirectory()) { break; } }
    【解决方案5】:

    即使这个线程有点老,我想知道以下解决方案是否有问题:

    package service;
    
    import java.io.File;
    
    public class FileService {
    
        public static synchronized boolean mkdirs( File dir ) {
            return dir.mkdirs();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-14
      相关资源
      最近更新 更多