设计模式3:单例模式:volatile关键字能不能解决多线程计数问题?

先说结论不能:
代码实测下:

public class Counter {
    
    
 
    public volatile static int count = 0;
 
    public static void inc() {
    
    
 
        //这里延迟1毫秒,使得结果明显
        try {
    
    
            Thread.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
 
        count++;
    }
 
    public static void main(String[] args) {
    
    
 
        //同时启动1000个线程,去进行i++计算,看看实际结果
 
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Counter.inc();
                }
            }).start();
        }
 
        //这里每次运行的值都有可能不同,可能为1000
        System.out.println("运行结果:Counter.count=" + Counter.count);
    }
}

执行环境:jdk1.8.0_372 CPU:intel i7-4960 2.6GHz
执行结果:

volatile不是可以保证线程从主存读取最新数据吗,为什么volatile解决不了多线程计数问题?

  • 例如假如线程A,线程B在进行read,load 操作时,发现主内存中count的值都是5,那么都会加载这个最新的值。
    在线程A对count进行修改之后,会write到主内存中,主内存中的count变量就会变为6。

  • 由于count被volatile修饰,其他线程保存的count值作废。这个过程其实是MESI协议。Modify,Exclusive,Shared,Invalid. 当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

  • 所以,线程B更新自己的缓存值变为6,再加1变为7,这不是没有问题吗?问题出在哪里?

  • cout++可以拆分成三步:

    1、线程读取i
    2、temp = i + 1
    3、i = temp
    

    当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意:此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作。注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6, 然后A线程执行了 i = temp (6)的操作,i的值会立即刷新到主存,并通知其他线程保存的 i 值失效。 之后B线程需要重新读取 i 的值,重新读取后B线程保存的 i 变成了6,B线程保存的 temp 也是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1。

深度剖析Java的volatile实现原理,再也不怕面试官问了
为什么volatile能保证有序性不能保证原子性
volatile关键字并不能作为线程计数器
Volatile与多线程

猜你喜欢

转载自blog.csdn.net/zhangjin1120/article/details/131431123