我正在试图弄清楚下面的代码是否存在任何潜在的并发问题。具体而言,可见性问题与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()确保所做的任何更改线程都被写回主内存?否则什么时候更改使得线程本地写回主内存,如果没有退出线程?
答案 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());
这样就不会有任何共享状态。