Java 为什么volatile plus synchronized不';不行?

Java 为什么volatile plus synchronized不';不行?,java,concurrency,Java,Concurrency,我试图理解java中的并发性。我知道synchronized,它在对象上创建了一个监视器,然后其他线程就不能对这个对象进行操作了。Volatile是关于处理器缓存的,如果我使用它,所有线程都不会创建对象的副本。因此,在我看来,如果我运行这段代码,我将得到正确的计数器值(40000)。但我错了 public class Main { private static volatile Integer counter = 0; public static void main(String[] arg

我试图理解java中的并发性。我知道synchronized,它在对象上创建了一个监视器,然后其他线程就不能对这个对象进行操作了。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++;
            }
        }
    }
}
}
公共类主{
专用静态易失性整数计数器=0;
公共静态void main(字符串[]args)引发InterruptedException{
线程=新计数器();
螺纹2=新计数器();
thread.start();
thread2.start();
thread.join();
螺纹2.连接();
系统输出打印项次(计数器);
}
公共静态类计数器扩展线程{
@凌驾
公开募捐{
对于(int i=0;i<20000;i++){
已同步(计数器){
计数器++;
}
}
}
}
}
但如果我使用syncronized方法,我将得到正确的结果:

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();

        }
    }
}
}
公共类主{
专用静态易失性整数计数器=0;
公共静态void main(字符串[]args)引发InterruptedException{
线程=新计数器();
螺纹2=新计数器();
thread.start();
thread2.start();
thread.join();
螺纹2.连接();
系统输出打印项次(计数器);
}
公共同步静态无效增量(){
计数器++;
}
公共静态类计数器扩展线程{
@凌驾
公开募捐{
对于(int i=0;i<20000;i++){
增量();
}
}
}
}

那个么问题-为什么synchronized对Integer对象不起作用???

您在非final字段上使用了
synchronized
块。使用
计数器+++
时,由于整数是不可变的,因此新的引用将分配给
计数器
。请检查此答案以了解更多详细信息-

您可以使用
ReentrantLock
而不是synchronized,但另一个问题是,您没有在volatile字段上使用原子操作。您应该改用
AtomicInteger

导入java.util.concurrent.AtomicInteger;
公共班机{
私有静态最终AtomicInteger计数器=新的AtomicInteger(0);
公共静态void main(字符串[]args)引发InterruptedException{
线程=新计数器();
螺纹2=新计数器();
thread.start();
thread2.start();
thread.join();
螺纹2.连接();
系统输出打印项次(计数器);
}
公共静态类计数器扩展线程{
@凌驾
公开募捐{
对于(int i=0;i<20000;i++){
counter.getAndIncrement();
}
}
}
}
作为参考,使用lock时,您不需要volatile:

import java.util.concurrent.locks.Lock;
导入java.util.concurrent.locks.ReentrantLock;
公共班机{
专用静态整数计数器=0;
private static final Lock=new ReentrantLock();
公共静态void main(字符串[]args)引发InterruptedException{
线程=新计数器();
螺纹2=新计数器();
thread.start();
thread2.start();
thread.join();
螺纹2.连接();
系统输出打印项次(计数器);
}
公共静态类计数器扩展线程{
@凌驾
公开募捐{
对于(int i=0;i<20000;i++){
lock.lock();
计数器++;
lock.unlock();
}
}
}
}

您误解了几件事:

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

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

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


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

    需要注意的关键是,
    Integer
    是一个不可变的类型,
    counter++
    实际上是将一个引用分配给另一个
    Integer
    对象到
    counter
    。然后duplink提供了答案。(事实上,
    计数器
    也是
    易失的
    与此无关。)“重复”回答了一个完全不同的问题。副本讲述了一个常见的新手错误:新手编写
    synchronized(x)
    ,然后将不同的对象引用分配给
    x
    。但是这本书的作者