class Counter
{
public int i=0;
public void increment()
{
i++;
System.out.println("i is "+i);
System.out.println("i/=2 executing");
i=i+22;
System.out.println("i is (after i+22) "+i);
System.out.println("i+=1 executing");
i++;
System.out.println("i is (after i++) "+i);
}
public void decrement()
{
i--;
System.out.println("i is "+i);
System.out.println("i*=2 executing");
i=i*2;
System.out.println("i is after i*2"+i);
System.out.println("i-=1 executing");
i=i-1;
System.out.println("i is after i-1 "+i);
}
public int value()
{
return i;
} }
class ThreadA
{
public ThreadA(final Counter c)
{
new Thread(new Runnable(){
public void run()
{
System.out.println("Thread A trying to increment");
c.increment();
System.out.println("Increment completed "+c.i);
}
}).start();
}
}
class ThreadB
{
public ThreadB(final Counter c)
{
new Thread(new Runnable(){
public void run()
{
System.out.println("Thread B trying to decrement");
c.decrement();
System.out.println("Decrement completed "+c.i);
}
}).start();
}
}
class ThreadInterference
{
public static void main(String args[]) throws Exception
{
Counter c=new Counter();
new ThreadA(c);
new ThreadB(c);
}
}
在上面的代码中,ThreadA首先访问Counter对象并递增值并执行一些额外的操作。 ThreadA第一次没有i的缓存值。但是在执行i ++之后(在第一行),它将获得缓存值。稍后更新值并获得24.根据程序,因为变量i不是易失性的,所以更改将在ThreadA的本地缓存中完成,
现在,当ThreadB访问decrement()方法时,i的值由ThreadA更新,即24.这怎么可能?
答案 0 :(得分:5)
假设线程不会看到其他线程对共享数据所做的每次更新都不合适,因为假设所有线程将立即看到彼此的更新。
重要的是要考虑到没有看到更新的可能性 - 不要依赖它。
除了没有看到来自其他线程的更新之外还有另一个问题,请注意 - 所有操作都在"读取,修改,写入"感觉......如果另一个线程在您阅读之后修改了该值,您基本上会忽略它。
例如,假设我们到达此行时i
为5:
i = i * 2;
...但在其中途,另一个线程将其修改为4。
该行可以被认为是:
int tmp = i;
tmp = tmp * 2;
i = tmp;
如果第二个帖子在"扩展"中的第一行之后将i
更改为4版本,那么即使i
是易变的,4的写入仍将有效丢失 - 因为到那时,tmp
为5,它将加倍为10,然后将写出10。
答案 1 :(得分:0)
如JLS 8.3.1.4
中所述:
Java编程语言允许线程访问共享 变量(第17.1节)。通常,确保共享变量 一致且可靠地更新,线程应确保它具有 通过获得锁定来独占使用这些变量, 通常,对这些共享变量实施互斥........ 字段可能是 声明为volatile,在这种情况下,Java内存模型可以确保这一点 所有线程都看到变量的一致值
尽管并非总是如此,但线程之间的共享值仍有可能不一致且可靠地更新,这将导致一些不可预测的程序结果。在下面给出的代码中
class Test {
static int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
如果,一个线程重复调用方法一(但总共不超过Integer.MAX_VALUE次),另一个线程重复调用方法二,则方法二偶尔会打印一个大于值的j的值i,因为该示例不包括同步,并且i和j的共享值可能无序更新。
但是,如果您将i
和j
声明为volatile
,则允许方法1和方法2同时执行,但保证访问i
和j
的共享值j
与每个线程执行程序文本时出现的次数完全相同,顺序完全相同。因此, i
的共享值永远不会大于{{1}}的共享值,因为对i的每次更新都必须在更新为j之前反映在i的共享值中
答案 2 :(得分:0)
现在我开始知道这些线程没有缓存常见对象(多个线程共享的对象)。由于该对象很常见,Java内存模型非常智能,可以在线程缓存时识别出常见对象,从而产生令人惊讶的结果。
答案 3 :(得分:0)
怎么可能呢?
因为JLS中没有任何地方说值必须在一个线程中缓存。
这就是规范所说的:
如果你有一个非易失性变量x
,并且它由一个帖子T1
更新,则无法保证T2
能够观察到x
的更改按T1
。保证T2
看到T1
更改的唯一方法是happens-before
关系。
在某些情况下,某些Java实现会在线程内缓存非易失性变量。换句话说,您不能依赖缓存的非易失性变量。