在这种情况下,使用volatile关键字足够吗?

3
以下是我的应用程序的启动/停止过程,启动/停止代码由单线程的ThreadPoolExecutor处理,因此我保证同一时间只有一个线程可以处于活动状态。 我问的是isRunning变量。 将变量设置为volatile是否足够? 该变量将从不同的线程访问(但同时只有一个!)
编辑: 添加变量读取(startProcedure()和stopProcedure()的开头)。 我忘了那部分,对此我表示歉意。
编辑2: 我认为可能很难注意到,但startProcedure()和stopProcedure()是用于创建startQuery和stopQuery的函数-线程使用的可运行项。
public final class Work {

    private static final ThreadPoolExecutor processor = new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1),
            new ThreadPoolExecutor.DiscardPolicy());
    private static final Runnable startQuery = Work::startProcedure,
            stopQuery = Work::stopProcedure;

    private static boolean isRunning = false;

    private Work() {}

    private static void startProcedure() {
        if (isRunning) return;
        isRunning = true;
        //<some code>
    }

    private static void stopProcedure() {
        if (!isRunning) return;
        //<some code>
        isRunning = false;
    }

    //------Public API

    public static void start() {
        processor.execute(startQuery);
    }

    public static void stop() {
        processor.execute(stopQuery);
    }

}

1
你的示例仅显示对isRunning变量的赋值。如果没有任何代码检查该变量,则声明方式并不重要。如果你的示例中有未展示的代码,那么任何人都很难以有意义地对其进行评论。 - Solomon Slow
@SolomonSlow 你说得对,我忘记添加变量读取部分了。我刚刚在 startProcedure()stopProcedure() 的开头添加了它。 - WLTY
@akuzminykh 再次阅读代码,startProcedure()stopProcedure() 正在被线程使用,没有其他代码线程执行。 - WLTY
1
@WLTY 当然,这就是“volatile”的意义所在。 - akuzminykh
@WLTY 然而,只是指出来,当只涉及可见性时,“volatile”在任何情况下都足够了。如果你的代码因为错误的同步而出问题,那就是另一回事了。 - akuzminykh
显示剩余5条评论
2个回答

2
首先,volatile 与 "线程安全" 几乎没有关系。 volatile 关注的是可见性及其提供的保证;具体而言,它围绕着 "happens-before" 原则工作(我不会深入讨论这一点)。
你的情况有点有趣:你只有一个线程。因此,真正的问题是,start 方法中执行的操作是否对下一个 stop 方法可见?换句话说,ThreadPoolExecutor::execute 是否提供任何可见性保证?
在我看来,答案是:是的,你根本不需要用到 volatile。实现 ThreadPoolExecutorExecutorService 说:

内存一致性效果:在将 Runnable 或 Callable 任务提交给 ExecutorService 之前,在线程中执行的操作发生在该任务执行的任何操作之前...

我理解的方式是:在 "start" 线程中执行的操作将发生在 "stop" 线程中执行的操作之前。但是,只有在像你的示例中依次调用(并等待)startstop 时才能做出此保证。一旦你更改了内部实现,这将不起作用。

你可能是对的,但另一方面,引用部分可能意味着Runnable的提交将可见,因此ExecutorService.submit()可以在多个线程中安全使用。这里有更多信息:https://dev59.com/g3HYa4cB1Zd3GeqPGwzr - WLTY

0

是的,volatile关键字对你的使用情况已经足够好了。

volatile关键字用于将Java变量标记为存储在主内存中。更准确地说,这意味着每次读取volatile变量都将从计算机的主内存中读取,而不是从CPU缓存中读取,并且每次写入volatile变量都将写入主内存,而不仅仅是写入CPU缓存。


1
没有所谓的“主内存”。如果你引用了什么东西,可能要包括这个引用来自哪里。而简单的“是”也不是一个答案。 - Eugene
请用您的理解纠正并启发我,分享我哪里错了。 - Suman
Volatile并不意味着每次读取都会发生在计算机的主内存中,这是一个常见的误解。如果每次都从主内存中读/写易失性变量,性能影响将非常令人失望。实际成本取决于CPU架构,它在更广泛的上下文中使用诸如MESI之类的协议共享一致性高速缓存行(“监视其他缓存”),强制当两个线程从相同的内存地址读取时,它们永远不应同时读取不同的值。https://software.rajivprab.com/2018/04/29/myths-programmers-believe-about-cpu-caches/ - Pedro Lourenço

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接