Java对象间同步

6

我希望弄清楚Java中synchronized的性能影响。我有几个简单的类:

public class ClassOne {

    private ClassTwo classTwo = new ClassTwo();

    public synchronized void setClassTwo(int val1, int val2) {
        classTwo.setVal(val1);
        classTwo.setVal2(val2);
    }

    public static void main(String[] args) {
        ClassOne classOne = new ClassOne();
        classOne.setClassTwo(10, 100);
    }

}

public class ClassTwo {

    private int val;
    private int val2;

    public synchronized void setVal(int val) {
        this.val = val;
    }

    public synchronized void setVal2(int val2) {
        this.val2 = val2;
    }

}

因此,正如您在前面的示例中所看到的,我正在对ClassOne.setClassTwo和ClassTwo.setVal以及ClassTwo.setVal2进行同步。 我想知道的是,如果我删除ClassTwo.setVal和ClassTwo.setVal2上的同步,是否会完全相同的性能,就像这样:

public class ClassTwo {

    private int val;
    private int val2;

    public void setVal(int val) {
        this.val = val;
    }

    public void setVal2(int val2) {
        this.val2 = val2;
    }

}

在这种情况下(假设没有其他类使用这些类),它们在功能上是等效的,但想知道有多少额外开销(如果有)会出现在更多同步操作中。

2
你有多少并发线程? - Elliott Frisch
1
为什么不自己试试呢?你可以在Java中测量时间。 - Jashaszun
2
让它承受许多线程的重负,这样就会有所不同。您不需要将setVal方法同步,这在重负下可以节省一些时间。 - Rocky Pulley
它们在功能上是等效的 - 我不这么认为.. public synchronized void setClassTwo - 你实际上正在同步class1的一个实例。setVal()setVal2()上的synchronized关键字是必要的,以确保除运行setClassTwo()的线程之外的其他线程不会更新ClassTwo实例的值。 在setVal()上进行同步会锁定* ClassTwo *。 - TheLostMind
1
创建一个有意义的基准测试并不是一件直截了当的事情。请参阅此文章 https://wikis.oracle.com/display/HotSpotInternals/MicroBenchmarks - Alexander Pogrebnyak
3个回答

5

会有额外的开销吗?是的。

会有很多额外开销吗?取决于情况。
如果只有一个线程,那么答案是“否”,即使在古老的时代,无竞争同步也很快,据说现在它们变得更好了。

那么如果有多个线程会发生什么?嗯,这就是问题所在:你发布的两个版本并不具备功能上的等价性。为什么?因为你调用的子方法是公共类的公共方法。因此,它们可以在setClassTwo之外被调用,从而没有同步。

另一件需要注意的事情是它们在不同的监视器上进行同步。第二个版本只在一个监视器上同步,而原始版本则在两个监视器上同步。

简而言之

synchronized留在需要同步的方法上,不要仅仅期望调用者进行同步(除非它嵌入到类API中)。如果调用方法正确地进行了同步,就不会有竞争,并且开销会非常小,如果它们以某种方式失败(例如,通过直接调用你的方法),那么你就会遇到竞争和更大的开销-但你仍然拥有线程安全性。


谢谢你的回答。当我说它们在这种情况下是“功能等效”的时候,我应该补充说。 - mainstringargs
@dubdubdubdot - 在这种情况下,只有一个线程。 :P。因此,同步实际上是无用的。(除非您考虑锁定/解锁的额外开销..) - TheLostMind
@dubdubdubdot 很难想象在只有这两个类和一个单线程的情况下它们会在功能上等效。一旦引入锁定classTwo对象的人,即使不触及变量,它们也是不同的。 - Ordous

1
在第一种情况下,您可以创建多个线程并直接在ClassTwo上调用setVal(),而不必担心内存不一致(ClassTwo上的setVal()是同步的)。 在第二种情况下,如果您运行多个线程并直接调用setVal(),则必须准备好出现意外结果。此外,如果您始终确信setVal()只会从setClassTwo()中调用,则建议您使用同步块在Class2实例上进行同步,并将setVal()和setVal2()保持同步。经验法则-只同步可以同时访问的内容。

2
与所有经验法则一样 - 还有一个相反的规则:同步任何可以同时访问并处理共享状态的内容,除非您明确确保了并发访问已得到妥善处理。在这种情况下,这两个规则之间的冲突凸显出另一个小问题 - 如果一个对象在线程之间共享,则不应允许其他对象修改该状态 - 在维护适当的同步的同时,在该对象内部进行修改。 - Ordous
@Ordous - 嗯...完全同意 :) - TheLostMind

1
如果您计划使用ClassTwo,同步方式也会受到影响。如果写入很少,读取很频繁,则在更大的规模下使用读写锁可能会提高性能。通常情况下,读取仅在写入处理过程中受到阻碍,因为读锁由多个读线程共享,而写锁会阻塞所有线程直到写入完成。

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html

我希望这可以帮到你。

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