1
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

4 回答 4

5

假设线程不会看到其他线程对共享数据所做的每个更新,这与假设所有线程立即看到彼此的更新一样不合适。

重要的是要考虑到看不到更新的可能性——不要依赖它。

请注意,除了看不到其他线程的更新之外,还有另一个问题 - 您的所有操作都在“读取、修改、写入”的意义上进行......如果另一个线程在您阅读后修改了该值,您基本上会忽略它。

例如,假设i当我们到达这条线时是 5:

i = i * 2;

...但是在它进行到一半时,另一个线程将其修改为 4。

这条线可以被认为是:

int tmp = i;
tmp = tmp * 2;
i = tmp;

如果第二个线程i在“扩展”版本的第一行之后更改为 4,那么即使i是 volatile 的写入 4 仍然会有效丢失 - 因为到那时,tmp是 5,它将加倍到 10,然后10 会被写出来。

于 2013-03-26T20:23:18.807 回答
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 次),而另一个线程重复调用方法二,那么方法二偶尔会打印出大于 i 值的 j 值,因为该示例不包括同步,并且 i 和 j 的共享值可能会乱序更新。
但是,如果您声明iandjvolatile,这允许方法一和方法二同时执行,但保证访问 和 的共享值发生的次数ij顺序完全相同,就像它们在执行期间发生的一样每个线程的程序文本。因此,共享值j永远不会大于i,因为在更新 j 之前,对 i 的每次更新都必须反映在 i 的共享值中

于 2013-03-26T20:44:19.487 回答
0

现在我知道公共对象(由多个线程共享的对象)不会被这些线程缓存。由于对象是常见的,Java 内存模型足够智能,可以识别线程缓存的常见对象可能会产生令人惊讶的结果。

于 2013-03-27T18:16:43.820 回答
0

这怎么可能?

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

这就是规范所说的:

如果您有一个非易失性变量x,并且它是由一个线程更新的T1,则无法保证T2可以观察到xby的变化T1。保证T2看到变化的唯一方法T1是建立happens-before关系。

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

于 2013-07-18T14:39:58.863 回答