即使没有volatile,线程值也不会被线程缓存?

时间:2013-03-26 20:20:51

标签: java multithreading volatile

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.这怎么可能?

4 个答案:

答案 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的共享值可能无序更新。
但是,如果您将ij声明为volatile,则允许方法1和方法2同时执行,但保证访问ij的共享值j与每个线程执行程序文本时出现的次数完全相同,顺序完全相同。因此, i的共享值永远不会大于{{1}}的共享值,因为对i的每次更新都必须在更新为j之前反映在i的共享值中

答案 2 :(得分:0)

现在我开始知道这些线程没有缓存常见对象(多个线程共享的对象)。由于该对象很常见,Java内存模型非常智能,可以在线程缓存时识别出常见对象,从而产生令人惊讶的结果。

答案 3 :(得分:0)

  

怎么可能呢?

因为JLS中没有任何地方说值必须在一个线程中缓存。

这就是规范所说的:

如果你有一个非易失性变量x,并且它由一个帖子T1更新,则无法保证T2能够观察到x的更改按T1。保证T2看到T1更改的唯一方法是happens-before关系。

在某些情况下,某些Java实现会在线程内缓存非易失性变量。换句话说,您不能依赖缓存的非易失性变量。