此
之间的性能是否有任何差异synchronized void x() {
y();
}
synchronized void y() {
}
和这个
synchronized void x() {
y();
}
void y() {
}
答案 0 :(得分:16)
是的,还有一个额外的性能成本,除非并且直到JVM内联对y()
的调用,现代JIT编译器将以相当短的顺序执行调用。首先,考虑一下您在课堂外可见y()
的情况。在这种情况下,JVM必须检查输入y()
以确保它可以进入对象的监视器;当呼叫来自x()
时,此检查将始终成功,但不能跳过,因为呼叫可能来自课外的客户端。这笔额外的支票费用很低。
此外,请考虑y()
为private
的情况。在这种情况下,编译器仍然不会优化同步;请参阅以下反汇编的空y()
:
private synchronized void y();
flags: ACC_PRIVATE, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
According to the spec's definition of synchronized
,synchronized
块或方法的每个入口都会对对象执行锁定操作,并执行解锁操作。在锁定计数器降至零之前,没有其他线程可以获取该对象的监视器。据推测,某种静态分析可以证明private synchronized
方法只能从其他synchronized
方法中调用,但Java的多源文件支持最多会使脆弱,甚至忽略反射。 This means that the JVM must still increment the counter on entering y()
:
调用
synchronized
方法的监视条目,并在其返回时监视退出,由Java虚拟机的方法调用和返回指令隐式处理,就好像 monitorenter 和 monitorexit 被使用。
@AmolSonawane correctly notes JVM可以通过执行 lock coarsening 在运行时优化此代码,实质上是内联y()
方法。在这种情况下,在JVM决定执行JIT优化之后,从x()
到y()
的调用不会产生任何额外的性能开销,但当然直接调用{来自任何其他位置的{1}}仍然需要单独获取监视器。
答案 1 :(得分:8)
Benchmark Mean Mean error Units
c.a.p.SO18996783.syncOnce 21.003 0.091 nsec/op
c.a.p.SO18996783.syncTwice 20.937 0.108 nsec/op
=>没有统计差异。
查看生成的程序集显示已执行锁定粗化,y_sync
已在x_sync
中内联,但已同步。
完整结果:
Benchmarks:
# Running: com.assylias.performance.SO18996783.syncOnce
Iteration 1 (5000ms in 1 thread): 21.049 nsec/op
Iteration 2 (5000ms in 1 thread): 21.052 nsec/op
Iteration 3 (5000ms in 1 thread): 20.959 nsec/op
Iteration 4 (5000ms in 1 thread): 20.977 nsec/op
Iteration 5 (5000ms in 1 thread): 20.977 nsec/op
Run result "syncOnce": 21.003 ±(95%) 0.055 ±(99%) 0.091 nsec/op
Run statistics "syncOnce": min = 20.959, avg = 21.003, max = 21.052, stdev = 0.044
Run confidence intervals "syncOnce": 95% [20.948, 21.058], 99% [20.912, 21.094]
Benchmarks:
com.assylias.performance.SO18996783.syncTwice
Iteration 1 (5000ms in 1 thread): 21.006 nsec/op
Iteration 2 (5000ms in 1 thread): 20.954 nsec/op
Iteration 3 (5000ms in 1 thread): 20.953 nsec/op
Iteration 4 (5000ms in 1 thread): 20.869 nsec/op
Iteration 5 (5000ms in 1 thread): 20.903 nsec/op
Run result "syncTwice": 20.937 ±(95%) 0.065 ±(99%) 0.108 nsec/op
Run statistics "syncTwice": min = 20.869, avg = 20.937, max = 21.006, stdev = 0.052
Run confidence intervals "syncTwice": 95% [20.872, 21.002], 99% [20.829, 21.045]
答案 2 :(得分:2)
为什么不测试它??我跑了一个快速的基准。在循环中调用benchmark()
方法进行预热。这可能不是非常准确,但它确实显示了一些一致的有趣模式。
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println("+++++++++");
benchMark();
}
}
static void benchMark() {
Test t = new Test();
long start = System.nanoTime();
for (int i = 0; i < 100; i++) {
t.x();
}
System.out.println("Double sync:" + (System.nanoTime() - start) / 1e6);
start = System.nanoTime();
for (int i = 0; i < 100; i++) {
t.x1();
}
System.out.println("Single sync:" + (System.nanoTime() - start) / 1e6);
}
synchronized void x() {
y();
}
synchronized void y() {
}
synchronized void x1() {
y1();
}
void y1() {
}
}
结果(过去10年)
+++++++++
Double sync:0.021686
Single sync:0.017861
+++++++++
Double sync:0.021447
Single sync:0.017929
+++++++++
Double sync:0.021608
Single sync:0.016563
+++++++++
Double sync:0.022007
Single sync:0.017681
+++++++++
Double sync:0.021454
Single sync:0.017684
+++++++++
Double sync:0.020821
Single sync:0.017776
+++++++++
Double sync:0.021107
Single sync:0.017662
+++++++++
Double sync:0.020832
Single sync:0.017982
+++++++++
Double sync:0.021001
Single sync:0.017615
+++++++++
Double sync:0.042347
Single sync:0.023859
看起来第二种变体确实略微更快。
答案 3 :(得分:1)
测试可以在下面找到(你必须猜测一些方法做什么,但没有复杂):
它用100个线程测试它们,并在70%的线程完成后开始计算平均值(作为预热)。
最后将它打印出来一次。
Zynced Cum: 3.2017985649516254E11
Zynced Conv: 8.11945143126507E10
Single Cum: 4.747368153507841E11
Single Conv: 8.277793176290959E10
MovingAverage.Cumulative add基本上是(原子地执行): average =(average *(n)+ number)/(++ n);
MovingAverage.Converging你可以查找但使用另一个公式。
50秒预热后的结果:
使用:jiterations - &gt;百万强>
Zynced Cum: 7.462005651190714E11
Zynced Conv: 9.03751742946726E11
Single Cum: 9.088230941676143E11
Single Conv: 9.09877020004914E11
纳秒平均值。这真的没什么,甚至表明 zynced需要更少的时间。
使用:jiterations - &gt;原* 10 (需要更长的时间)
Zynced Cum: 6.9167088486E10
Zynced Conv: 6.9167088486E10
Single Cum: 6.9814404337E10
Single Conv: 6.9814404337E10
正如您所看到的,结果表明它并没有太大的区别。 zynced实际上有最后30%完成的较低平均时间。
每个一个线程(迭代= 1)和jiterations =原始* 100;
Single Cum: 2.940499529542545E8
Single Conv: 5.0342450600964054E7
Zynced Cum: 1.1930525617915475E9
Zynced Conv: 6.672312498662484E8
在同一个线程环境(删除Threads.async调用)
使用:jiterations - &gt;原* 10
public static final class Test {
final int iterations = 100;
final int jiterations = 10000000;
final int count = (int) (0.7 * iterations);
final AtomicInteger finishedSingle = new AtomicInteger(iterations);
final AtomicInteger finishedZynced = new AtomicInteger(iterations);
final MovingAverage.Cumulative singleCum = new MovingAverage.Cumulative();
final MovingAverage.Cumulative zyncedCum = new MovingAverage.Cumulative();
final MovingAverage singleConv = new MovingAverage.Converging(0.5);
final MovingAverage zyncedConv = new MovingAverage.Converging(0.5);
// -----------------------------------------------------------
// -----------------------------------------------------------
public static void main(String[] args) {
final Test test = new Test();
for (int i = 0; i < test.iterations; i++) {
test.benchmark(i);
}
Threads.sleep(1000000);
}
// -----------------------------------------------------------
// -----------------------------------------------------------
void benchmark(int i) {
long start = System.nanoTime();
for (int j = 0; j < jiterations; j++) {
a();
}
long elapsed = System.nanoTime() - start;
int s = this.finishedSingle.decrementAndGet();
if ( s <= count ) {
singleCum.add (elapsed);
singleConv.add(elapsed);
}
if ( s == 0 ) {
System.out.println(elapsed);
System.out.println("Single Cum:\t\t" + singleCum.val());
System.out.println("Single Conv:\t" + singleConv.val());
System.out.println();
}
long zstart = System.nanoTime();
for (int j = 0; j < jiterations; j++) {
az();
}
long elapzed = System.nanoTime() - zstart;
int z = this.finishedZynced.decrementAndGet();
if ( z <= count ) {
zyncedCum.add(elapzed);
zyncedConv.add(elapzed);
}
if ( z == 0 ) {
// Just to avoid the output not overlapping with the one above
Threads.sleep(500);
System.out.println();
System.out.println("Zynced Cum: \t" + zyncedCum.val());
System.out.println("Zynced Conv:\t" + zyncedConv.val());
System.out.println();
}
}
synchronized void a() { b(); }
void b() { c(); }
void c() { d(); }
void d() { e(); }
void e() { f(); }
void f() { g(); }
void g() { h(); }
void h() { i(); }
void i() { }
synchronized void az() { bz(); }
synchronized void bz() { cz(); }
synchronized void cz() { dz(); }
synchronized void dz() { ez(); }
synchronized void ez() { fz(); }
synchronized void fz() { gz(); }
synchronized void gz() { hz(); }
synchronized void hz() { iz(); }
synchronized void iz() {}
}
这里的zynced似乎比较慢。订单约10。造成这种情况的原因可能是由于zynced每次都在运行,谁知道。没有能量尝试相反。
上次测试运行:
if ( user logged in )
{
getloggedmenu();
}
else
{
getUnloggedmenu()
}
public function getloogedmenu()
{
return set you menu here;
}
public function getUnloogedmenu()
{ return set you menu here;
}
结论,确实没有区别。
答案 4 :(得分:0)
在两种方法同步的情况下,您将锁定监视器两次。因此,第一种方法会再次产生额外的锁定开销。但是你的JVM可以通过锁定粗化来降低锁定成本,并且可以内联调用y()。
答案 5 :(得分:0)
没有区别。由于线程内容仅用于获取x()处的锁定。在x()处获取锁定的线程可以在y()处获取锁定而不会发生任何争用(因为这只是在某个特定时间可以到达该点的线程)。所以在那里放置同步没有效果。