这是同步代码块的安全方法吗?

在我们的应用程序中,有一段代码不断运行以读取和调整文件。只是为了让您了解正在发生的事情:


public void run() {

    try {

        while(true) { //Yeah, I know...

            Path currentFileName = getNextFile();

            String string = readFile(currentFileName);

            Files.deleteFile(currentFileName);

            string = string.replaceAll("Hello", "Blarg");

            writeFile(currentFileName);

        }

    } catch (Exception e) {

        System.err.println("It's all ogre now.");

        e.printStackTrace(System.err);

    }

}

我们代码中的其他地方有一个方法,它可能(但通常)不在与上述代码相同的线程上运行,我们用它来退出应用程序。


private void shutdown() {

    if(fileReader != null)

        fileReader = null;


    System.exit(0); //Don't blame me, I didn't write this code

}

很明显,此代码中存在潜在的竞争条件,如果在shutdown()检索文件和写回文件之间调用 if ,则可能会导致文件完全丢失。显然,这是不受欢迎的行为。


这段代码有上千个问题(超出了我在这里展示的范围),但我需要解决的主要问题是处理文件可以中途中断而没有追索权的不良行为。我提出的解决方案涉及简单地将 while 循环包装在一个块中,并在调用synchronized周围放置一个块。System.exitshutdown


所以我更改后的代码如下所示:


private Object monitor = new Object();

public void run() {

    try {

        while(true) {

            synchronized(monitor) {

                Path currentFileName = getNextFile();

                String string = readFile(currentFileName);

                Files.deleteFile(currentFileName);

                string = string.replaceAll("Hello", "Blarg");

                writeFile(currentFileName);

            }

        }

    } catch (Exception e) {

        System.err.println("It's all ogre now.");

        e.printStackTrace(System.err);

    }

}


private void shutdown() {

    synchronized(monitor) {

        if(fileReader != null)

            fileReader = null;


         System.exit(0);

    }

}

我主要担心的是System.exit(0);电话,我不确定电话幕后的总体行为。System.exit是否存在将释放锁的副作用的风险monitor,从而导致循环内容在导致 JVM 停止run之前被部分执行的风险?System.exit还是这段代码会保证执行过程永远不会在处理单个文件时尝试中途关闭?


注意:在一些纸上谈兵的程序员介入替代方案之前,我想指出,我在这里放置的是大约 4000 行代码的截断版本,所有代码都隐藏在一个类中。是的,这太可怕了。是的,这让我后悔我选择的职业。我不是在这里寻找这个问题的替代解决方案,我只是想确定这个特定的解决方案是否有效,或者是否存在一些严重的缺陷会阻止它像我预期的那样工作。


眼眸繁星
浏览 145回答 4
4回答

PIPIONE

还是这段代码会保证执行过程永远不会在处理单个文件时尝试中途关闭?此代码保证此处启动的关闭不会在处理单个文件的过程中发生。也许很明显,您代码中的其他地方可以调用System.exit,而您对此没有任何保护。您可能需要考虑防止System.exit被调用,然后让您的代码正常关闭(即通过方法的正常完成main)。

翻阅古今

万一你真的有多个线程调用不同的方法,synchronized这样使用实际上是一个聪明的主意,因为它处理了“多线程”的事情。您可以考虑缩小第一个块的范围:Path currentFileName = getNextFile();String string = readFile(currentFileName);synchronized(monitor) {单独读取文件应该不是问题。(当然,除非您此处的代码必须保证由 返回的 PathgetNextFile()得到完全处理)。

慕无忌1623718

如果代码在synchronized块中执行并且块在同一个对象上同步,并且synchronized循环中块中调用的方法while完全在与其调用者相同的线程上运行,则不存在文件相关进程被中断的风险通过调用System.exit. _这就是说,它看起来确实像是一个有争议的补丁,只是稍微改进了有争议的代码。可能还存在更具体的饥饿风险,因为显示的 while 循环似乎尽可能快地向这些文件操作发送垃圾邮件,因此在退出时尝试获取锁可能不会成功。探索的一般方向是将无限while循环转换为ScheduledExecutorService+ Runnable,使用自己的监视器执行每 x 时间量以防止对相同文件的重叠操作,并在shutdown调用该方法时优雅地终止它。

繁星淼淼

您可以使用关闭挂钩。来自 javadocs:关闭挂钩只是一个已初始化但未启动的线程。当虚拟机开始其关闭序列时,它将以某种未指定的顺序启动所有已注册的关闭挂钩,并让它们同时运行。由此,您可以从您的文件类中提供一个关闭挂钩,如下所示:public Thread getShutdownHook() {    return new Thread(() -> {        synchronized (monitor) {            // gracefully handle the file object        }    });}这将在调用时调用Runtime.getRuntime().exit()(由 调用System.exit())。由于它也在监视器对象上同步,如果其他线程正在使用该文件,关闭挂钩将阻塞直到它空闲为止。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java