并发,对象可见性

时间:2009-12-14 16:04:28

标签: java concurrency multithreading volatile

我正在试图弄清楚下面的代码是否存在任何潜在的并发问题。具体而言,可见性问题与volatile变量有关。 Volatile 定义为:此变量的值永远不会被线程本地缓存:所有读取和写入将直接进入“主存储器”

public static void main(String [] args)
{
    Test test = new Test();

    // This will always single threaded
    ExecutorService ex = Executors.newSingleThreadExecutor();

    for (int i=0; i<10; ++i)
        ex.execute(test);
}

private static class Test implements Runnable {
    // non volatile variable in question
    private int state = 0;

    @Override
    public void run() {
        // will we always see updated state value? Will updating state value
        // guarantee future run's see the value?
        if (this.state != -1)
            this.state++;
    }
}

对于上述单线程执行程序

是否可以使 test.state 非易失性?换句话说,每个连续的 Test.run()(它将按顺序而不是并发发生,因为再次执行者是单线程的),总是会看到更新的 test.state 值?如果没有,不退出 Test.run()确保所做的任何更改线程都被写回主内存?否则什么时候更改使得线程本地写回主内存,如果没有退出线程?

6 个答案:

答案 0 :(得分:4)

只要它只是一个线程就没有必要让它变得不稳定。如果您要使用多个线程,则不仅要使用volatile,还要同步。 增加数字不是原子操作 - 这是一种常见的误解。

public void run() {
    synchronize (this) {
        if (this.state != -1)
            this.state++;
    }
}

您也可以使用AtomicInteger#getAndIncrement()(如果之前不需要if),而不是使用同步。

private AtomicInteger state = new AtomicInteger();

public void run() {
    state.getAndIncrement()
}

答案 1 :(得分:3)

最初,我这样想:

  

如果任务总是由执行   同一个线程,就没有了   问题。但Excecutor由...产生   newSingleThreadExecutor()可能会创建   新线程替换那些   因任何原因被杀没有   保证何时更换   将创建线程或哪个线程   将创造它。

     

如果一个线程执行了一些写操作,那么   在新线程上调用start()   写入将对新的可见   线。但不能保证   该规则适用于这种情况。

但无可争辩的是正确的:创建一个没有足够障碍的正确ExecutorService以确保可见性几乎是不可能的。我忘了检测另一个线程的死亡是同步 - 与的关系。用于空闲工作线程的阻塞机制也需要一个障碍。

答案 2 :(得分:2)

是的,它是安全的,即使执行者在中间替换了它的线程。线程启动/终止也是同步点。

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

一个简单的例子:

static int state;
static public void main(String... args) {
    state = 0;                   // (1)
    Thread t = new Thread() {
        public void run() {
            state = state + 1;   // (2) 
        }
    };
    t.start();
    t.join();
    System.out.println(state);  // (3)
}

保证(1),(2),(3)排序良好,并按预期运行。

对于单线程执行程序,“任务保证按顺序执行”,它必须在开始下一个任务之前以某种方式检测一个任务的完成,这必然正确地同步不同的run()

答案 3 :(得分:0)

您的代码,特别是此位

            if (this.state != -1)
                    this.state++;

需要对状态值进行 atomic 测试,然后在并发上下文中增加状态。因此,即使您的变量是易变的并且涉及多个线程,您也会遇到并发问题。

但是你的设计是基于断言只有一个Test实例,,单个实例只被授予一个(相同的)线程。 (但请注意,单个实例实际上是主线程和执行程序线程之间的共享状态。)

我认为你需要使这些假设更加明确(例如,在代码中,使用ThreadLocal和ThreadLocal.get())。这是为了防范未来的错误(当其他开发人员可能不小心违反设计假设时),并防止对the Executor method you are using的内部实现做出假设,这可能在某些实现中只提供单线程执行器(即顺序执行)并且在每次执行execute(runnable)时都不一定是相同的线程。

答案 4 :(得分:0)

状态在这个特定代码中是非易失性是完全正常的,因为只有一个线程,只有该线程访问该字段。禁用在您拥有的唯一线程中缓存此字段的值只会影响性能。

但是如果你想在运行循环的主线程中使用state的值,你必须使字段变为volatile:

    for (int i=0; i<10; ++i) {
            ex.execute(test);
            System.out.println(test.getState());
    }

但是,即使这可能无法正常使用volatile,因为线程之间没有同步。

由于该字段是私有的,因此只有主线程执行可以访问此字段的方法时才会出现问题。

答案 5 :(得分:-1)

如果你的ExecutorService是单线程的,那么就没有共享状态,所以我看不出周围有什么问题。

然而,将Test课程的新实例传递给execute()的每次调用会不会更有意义?即。

for (int i=0; i<10; ++i)
    ex.execute(new Test());

这样就不会有任何共享状态。