Discuss / Java / 请问为什么不能用 volatile 代替 synchronized?

请问为什么不能用 volatile 代替 synchronized?

Topic source

廖神,前面说到 volatile 有两个作用:

  • 每次访问变量后,总是获取主内存的最新值

  • 每次修改变量后,立即回写到主内存

是不是意味着 volatile 可以达到同步变量的作用?

请问这一段代码里,为什么用 volatile 会导致结果不对呢?

public class Main {
    public static void main(String[] args) throws Exception {
        var add = new AddThread();
        var dec = new DecThread();
        add.start();
        dec.start();
        add.join();
        dec.join();
        System.out.println(Counter.count);
    }
}

class Counter {
    public volatile static int count = 0;
}

class AddThread extends Thread {
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Counter.count += 1;
        }
    }
}

class DecThread extends Thread {
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Counter.count -= 1;
        }
    }
}

廖雪峰

#2 Created at ... [Delete] [Delete and Lock User]

volatile只保证:

  1. 读主内存到本地副本;
  2. 操作本地副本;
  3. 回写主内存。

这3步多个线程可以同时进行。

半死态

#3 Created at ... [Delete] [Delete and Lock User]

是不是volatile不能保证变量的原子性的缘故?

廖雪峰

#4 Created at ... [Delete] [Delete and Lock User]

是保证时效性不是原子性

谢谢廖神回复!有点理解了,也就是说 volatile 只保证变量改动之后立刻回写,之后的新线程读取到的肯定是更新后的值,但之前已经获取过此变量本地副本的线程无法得知主内存中这个变量有了修改。

随风曦457

#6 Created at ... [Delete] [Delete and Lock User]

"volatile保证时效性不是原子性"  一语中的

在线程2修改count值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量count的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。

但是像i++或者i--自增自减操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:

   假如某个时刻变量count的值为1,

   线程1对变量进行自增操作,线程1先读取了变量count的原始值,然后线程1被阻塞了;

   然后线程2对变量进行自减操作,线程2也去读取变量count的原始值,由于线程1只是对变量count进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量count的缓存行无效,所以线程2会直接去主存读取count的值,发现count的值时1,然后进行加1操作,并把0写入工作内存,最后写入主存。

   然后线程1接着进行加1操作,由于已经读取了count的值,注意此时在线程1的工作内存中count的值仍然为1,所以线程1对count进行加1操作后count的值为2,然后将2写入工作内存,最后写入主存。

   那么两个线程分别进行了一次自增自减操作后,count值却为2。

GNGGIU_

#8 Created at ... [Delete] [Delete and Lock User]

两码事

一个是线程读取内存的时效的问题,一个是同步的问题

临界区的共享变量,你加volatile是没毛病.但是对于例题中的两个线程对一个变量不加锁的去读写,因为谁都不知道os的调度什么时候发生,所以保持时效性也没什么用


  • 1

Reply