首页 > 解决方案 > 为什么 volatile 加上 synchronized 不起作用?

问题描述

我试图理解java中的并发性。我知道同步,它在对象上创建一个监视器,然后另一个线程不能对这个对象进行操作。Volatile - 关于处理器缓存,如果我使用它,所有线程都不会创建对象的副本。所以,在我看来,如果我运行这段代码,我会得到正确的计数器值(40000)。但我不正确!

public class Main {

private static volatile Integer counter = 0;

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Counter();
    Thread thread2 = new Counter();
    thread.start();
    thread2.start();
    thread.join();
    thread2.join();
    System.out.println(counter);
}


public static class Counter extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20000; i++) {
            synchronized (counter){
                counter++;
            }
        }
    }
}
}

但是如果我使用同步方法,我会得到正确的结果:

public class Main {

private static volatile Integer counter = 0;

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Counter();
    Thread thread2 = new Counter();
    thread.start();
    thread2.start();
    thread.join();
    thread2.join();
    System.out.println(counter);
}

public synchronized static void increment(){
    counter++;
}

public static class Counter extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20000; i++) {
            increment();

        }
    }
}
}

所以问题 - 为什么 synchronized 对 Integer 对象不起作用?

标签: javaconcurrency

解决方案


你误解了几件事:

  1. volatile与 CPU 缓存无关。所有现代处理器都采用多级 CPU 缓存,对应用程序完全透明,因此应用程序不必关心它们是否从 L1、L2、L3、RAM 等获取。这是通过 CPU 实现一些缓存来完成的- 一致性协议,例如MESI或其变体。那么做什么volatile呢?它可以防止某些编译器优化。例如,如果您读取一个变量的值一次,volatile编译器可能会优化该变量的任何后续读取,因为它假定它不可能改变。使用volatile它不会删除那些额外的读取。

  2. synchronized关键字使用某个对象作为锁,但在您的情况下,您正在更改该对象。所以让我们假设线程 1 锁定在 Integer(0) 上,然后由于自动装箱,++ 操作将对象更改为 Integer(1)。您的第二个线程可以自由获取该锁,因为它不被任何人持有。

  3. 锁定/同步字符串(因为它们可以被实习)或布尔值或整数等是非常糟糕的主意。例如,字符串对象可以被实习,您可以让程序的多个部分尝试在同一个实例上获取锁定,尽管从他们的角度来看,这应该是一个不同的例子。布尔值 true/false 被缓存。从 -128 到 +127 的自动装箱整数会被缓存,因此如果您可能遇到与内部字符串相同的问题。

因此,对于同步,最好使用适当的锁并避免使用这个synchronized词。我什至会更进一步说这是java中的一个错误


推荐阅读