易失性和同步化

5

我还没有完全理解synchronized和volatile的区别。

我知道线程可以安全地在本地进行更改。从我迄今所读的内容来看,synchronized > volatile。

假设我有一个参数,它不是long或double,而是一个标准的整数(没有原子性)。

我有一个同步方法,在这个方法中我对这个整数进行了很多操作。所有线程都会得到这个整数的更新版本吗?还是我也必须声明它为volatile?

public class stackoverflow {

    private int x = 0;

    public synchronized void rechnen(){ 
        //dosomething   
    }
}

基本上,在 rechnen() 完成后,如果我有 10000 个线程,它们都会得到 x 的更新版本,因为我的方法是同步的吗?还是我也必须声明它为 volatile?

请在您的问题中标记您正在使用的编程语言。 - Robin Green
4个回答

6

是的,它们将得到更新的版本。synchronized 保证了两件事:变更的可见性和原子性。而volatile 只保证了变更的可见性。Java 保证了 synchronized 块中的代码不会被优化(通过混合块内和块外的命令),因此在结束 synchronized 块之后,对其中变量的任何更改都将对所有线程可见。


1
“在同步块结束后,所有线程对内部变量的更改将对所有线程可见。” 这当然是正确的,但我认为“Java保证在同步块内的代码不会被优化”是不正确的。 - v.ladynev
@v.ladynev 是的,你说得对。我写得不太好。我编辑了答案。我的想法是Java不会通过在synchronized块内外混合语句来优化代码。 - partlov
2
每次在同步块内对变量进行更改后,所有线程都能在同步块结束后看到这些更改,这并不完全正确。JLS只保证如果线程A更新了一个变量然后退出同步块,那么线程B将能够在使用相同对象进行同步之后看到更新。 - Solomon Slow

5

作为一个附注,@partlov已经回答了你的问题,但是还有一些其他事情你可能需要考虑。

不要使用synchronized作为修饰符

当声明一个方法为synchronized时,它会使用一个监视器。在Java中,每个Object都恰好是一个监视器,在这种情况下,类实例被使用。因此,你的示例有效地变成了:

public void rechnen(){ 
    synchronized(this) {
        //dosomething
    }   
}

现在这可能会造成问题,因为您正在泄露监视器。应用程序的其他部分可以使用相同的监视器来同步完全不相关的代码,这可能导致/导致性能下降,或者更糟的是,当它与相关时,可能会导致意外死锁。
所以主要建议是,始终保持您的监视器私有。所以像这样的东西:
public class stackoverflow {
    private final Object monitor = new Object();
    private int x = 0;

    public void rechnen(){
         synchronized(monitor) {
            //dosomething
         } 
    }
}

了解您的API

volatilesynchronized之间有许多用于特定并发目的的工具。其中大部分使用混合的volatilesynchronizedCAS操作。例如,AtomicInteger提供原子整数操作,其争用远少于通常由synchronized所看到的。因此,请尝试熟悉java.util.concurrent


3
是的,它们将获得更新版本,但仅当它们自己进入同步块时才会如此。如果它们没有在相同对象上进入同步块(锁定相同监视器),那么不能保证它们将看到更新内容。
如果您不希望其他线程进入同步块,则必须将变量声明为volatile。

2
它不必是相同的同步块。但是,它必须是一个在相同锁对象上同步的块。 - Solomon Slow

-2

声明私有的易变整数 x = 0; 这将符合您的需求。为了更好地理解,请参阅 AtomicInteger 的实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接