4

我编写了这个程序来测试mkdir()失败的场景。为什么会失败?

有时它工作正常,有时我得到:

无法创建 DIR :: myDir4 无法创建 DIR :: myDir4

最后我发现每个目录都被创建了......

在每次测试中,我都会删除所有创建的目录。

我尝试了这个,因为在我的项目中有 100 个线程试图测试和创建这样的目录......并且也以同样的方式失败......

public class DFS {
    static long time1 = System.currentTimeMillis();
    public static void main(String a[]) {
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
        new Thread(new CreteDir()).start();
    }
}

class CreteDir implements Runnable {
    public void run() {
        //Object obj = new Object();
        synchronized (this) {
        if(System.currentTimeMillis() - DFS.time1 > 10) {
            try {
                this.wait();
            }
            catch(InterruptedException ie) {
                ie.printStackTrace();
            }
        }
        File f1 = new File("myDir1");
        File f2 = new File("myDir2");
        File f3 = new File("myDir3");
        File f4 = new File("myDir4");
        File f5 = new File("myDir5");

        if (!f1.exists()&&!f1.mkdir()) {
            System.out.println("Cannot create DIR :: "+f1.getName());
        }
        if (!f2.exists()&&!f2.mkdir()) {
            System.out.println("Cannot create DIR :: "+f2.getName());
        }
        if (!f3.exists()&&!f3.mkdir()) {
            System.out.println("Cannot create DIR :: "+f3.getName());
        }
        if (!f4.exists()&&!f4.mkdir()) {
            System.out.println("Cannot create DIR :: "+f4.getName());
        }
        if (!f5.exists()&&!f5.mkdir()) {
            System.out.println("Cannot create DIR :: "+f5.getName());
        }
        this.notifyAll();
        }
    }
}
4

2 回答 2

11

你有一个竞争条件。

每个线程都会尝试检查每个目录,如果它还不存在则创建它。正在发生的事情是这样的:

  • 线程A测试myDir4并发现它不存在
  • 线程B测试myDir4并发现它不存在
  • 线程A创建myDir4...成功!
  • 线程B创建myDir4...失败!它已经存在。

这可能发生在任何目录中……或者根本不会发生……取决于操作系统如何调度 Java 线程等。


您的代码正在尝试在 上同步this,但尝试无效。这this将是CreteDir当前线程正在使用的实例......但每个线程都有不同的实例,因此实际上没有线程间同步。为了有效地同步,所有线程都需要在同一个对象上同步......但这会使您的多线程无效,因为粒度是错误的。

事实上,您的整个多线程策略需要重新考虑。这不是“真正的代码”这一事实意味着我们无法真正建议您如何做到这一点。

在您最后一条评论的字里行间阅读,我认为您有三种可能的策略:

  • 当目录不存在时,只需使用“全局”锁来同步创建目录。像这样的东西:

    // Test first without locking to reduce the concurrency bottleneck
    if (!dir.exists()) {
        synchronize (globalDirLock) {
            // Repeat the test while holding the lock
            if (!dir.exists()) { 
                if (!dir.mkdir()) {
                    System.out.println("OOOPS!");
                }
            }
        }
    }
    
  • 创建一个内存数据结构,每个目录有一个(锁定)对象。需要仔细填充该数据结构以避免竞争条件,即两个同时的客户端请求最终为单个目录创建两个锁定对象。

    (如果您稍微调整一下这个方案,您可能还可以使用锁定对象的存在来避免重复检查目录是否存在。检查涉及系统调用,并且在处理器开销方面非常重要。)

  • 只需忽略File.mkdir()返回的情况false。(也许做另一个File.exists()File.isDirectory()测试......以防万一。)

于 2012-10-07T10:49:30.807 回答
0

您可以尝试以下操作,而不是添加全局锁:

if (!f1.exists()&&!f1.mkdir()&&!f1.isDirectory()) {
            System.out.println("Cannot create DIR :: "+f1.getName());
        }

isDirectory()将告诉该目录是否已经创建。

于 2016-06-01T13:58:25.807 回答