如何在 Java 中以线程安全的方式使用 mkdirs?

Posted

技术标签:

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

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

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

谢谢

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

【问题讨论】:

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

在序列化所有内容的工作线程中创建所有目录。您可以使用 LooperHandler 轻松将调用 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();
    

【讨论】:

以上是关于如何在 Java 中以线程安全的方式使用 mkdirs?的主要内容,如果未能解决你的问题,请参考以下文章

您可以在 DTrace 中以多 CPU 安全的方式比较探针之间的值吗?

在JAVA中以编程方式监视JVM的堆栈区域? [复制]

如何在 C# 的线程中以编程方式复制 Excel 文件时修复访问拒绝错误

如何在 Swift 中以编程方式从 UIView 中删除安全区域?

Java多线程有哪几种实现方式? Java中的类如何保证线程安全? 请说明ThreadLocal的用法和适用场景

JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;