Java 线程没有缓存线程值,即使没有volatile?

Java 线程没有缓存线程值,即使没有volatile?,java,multithreading,volatile,Java,Multithreading,Volatile,在上面的代码中,ThreadA首先获得了对Counter对象的访问权,并在执行一些额外操作的同时增加了该值。ThreadA第一次没有i的缓存值。但在执行i++(在第一行)之后,它将获得缓存值。稍后,该值将更新并得到24。根据该程序,由于变量i不是易变的,因此更改将在ThreadA的本地缓存中进行 现在,当ThreadB访问decrement()方法时,i的值由ThreadA更新,即24。这怎么可能呢?假设线程不会看到其他线程对共享数据所做的每个更新,这与假设所有线程都会立即看到彼此的更新一样不合

在上面的代码中,ThreadA首先获得了对Counter对象的访问权,并在执行一些额外操作的同时增加了该值。ThreadA第一次没有i的缓存值。但在执行i++(在第一行)之后,它将获得缓存值。稍后,该值将更新并得到24。根据该程序,由于变量i不是易变的,因此更改将在ThreadA的本地缓存中进行


现在,当ThreadB访问decrement()方法时,i的值由ThreadA更新,即24。这怎么可能呢?

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

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

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

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

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); 
    }
}
。。。但中途,另一个线程将其修改为4

这条线可以被认为是:

i = i * 2;

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

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

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

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

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

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); 
    }
}
。。。但中途,另一个线程将其修改为4

这条线可以被认为是:

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

如:

Java编程语言允许线程访问共享的 变量(§17.1)。通常,为了确保共享变量 一致且可靠地更新,线程应确保 通过获得一个锁来独占使用这些变量, 按照惯例,强制这些共享变量互斥……。字段可能是 声明为volatile,在这种情况下,Java内存模型确保 所有线程都会看到变量的一致值

虽然并非总是如此,但线程之间的共享值仍有可能无法一致可靠地更新,这将导致程序的某些不可预测的结果。在下面给出的代码中

int tmp = i;
tmp = tmp * 2;
i = tmp;
如果一个线程重复调用方法一(但总共不超过Integer.MAX_VALUE次),而另一个线程重复调用方法二,那么方法二可能会偶尔打印一个大于i的j值,因为该示例不包括同步和,i和j的共享值可能会无序更新
但是,如果您将
i
j
声明为
volatile
,这将允许方法1和方法2同时执行,但可以保证访问
i
j
的共享值的次数和顺序完全相同,在每个线程执行程序文本的过程中出现。因此,对于
j
的共享值永远不会大于
i
的共享值,因为对i的每次更新必须在对j的更新发生之前反映在i的共享值中,如:

Java编程语言允许线程访问共享的 变量(§17.1)。通常,为了确保共享变量 一致且可靠地更新,线程应确保 通过获得一个锁来独占使用这些变量, 按照惯例,强制这些共享变量互斥……。字段可能是 声明为volatile,在这种情况下,Java内存模型确保 所有线程都会看到变量的一致值

虽然并非总是如此,但线程之间的共享值仍有可能无法一致可靠地更新,这将导致程序的某些不可预测的结果。在下面给出的代码中

int tmp = i;
tmp = tmp * 2;
i = tmp;
如果一个线程重复调用方法一(但总共不超过Integer.MAX_VALUE次),而另一个线程重复调用方法二,那么方法二可能会偶尔打印一个大于i的j值,因为该示例不包括同步和,i和j的共享值可能会无序更新
但是,如果您将
i
j
声明为
volatile
,这将允许方法1和方法2同时执行,但可以保证访问
i
j
的共享值的次数和顺序完全相同,在每个线程执行程序文本的过程中出现。因此,对于
j
的共享值永远不会大于
i
的共享值,因为每次对i的更新都必须反映在i的共享值中