同步本地variables

今天我遇到了org.jasig.cas.client.util.CommonUtils类的方法constructServiceUrl() 。 我以为他很奇怪:

 final StringBuffer buffer = new StringBuffer(); synchronized (buffer) { if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) { buffer.append(request.isSecure() ? "https://" : "http://"); } buffer.append(serverName); buffer.append(request.getRequestURI()); if (CommonUtils.isNotBlank(request.getQueryString())) { final int location = request.getQueryString().indexOf( artifactParameterName + "="); if (location == 0) { final String returnValue = encode ? response.encodeURL(buffer.toString()) : buffer.toString(); if (LOG.isDebugEnabled()) { LOG.debug("serviceUrl generated: " + returnValue); } return returnValue; } buffer.append("?"); if (location == -1) { buffer.append(request.getQueryString()); } else if (location > 0) { final int actualLocation = request.getQueryString() .indexOf("&" + artifactParameterName + "="); if (actualLocation == -1) { buffer.append(request.getQueryString()); } else if (actualLocation > 0) { buffer.append(request.getQueryString().substring(0, actualLocation)); } } } } 

为什么作者同步一个局部variables?

这是一个手动“ locking粗化 ”的例子,可能已经完成了性能提升。

考虑这两个片段:

 StringBuffer b = new StringBuffer(); for(int i = 0 ; i < 100; i++){ b.append(i); } 

与:

 StringBuffer b = new StringBuffer(); synchronized(b){ for(int i = 0 ; i < 100; i++){ b.append(i); } } 

在第一种情况下,StringBuffer必须获取并释放一个锁100次(因为append是一个同步方法),而在第二种情况下,锁只能被获取并释放一次。 这可以给你一个性能提升,可能是为什么作者做到了。 在某些情况下,编译器可以为你执行这个locking粗化 (但不能在循环结构周围,因为你可能会长时间持有locking)。

顺便说一下,编译器可以检测到一个对象不是从一个方法中“逃脱”,因此,因为没有其他线程可以访问该对象,所以完全取消了对象的获取和释放锁(locking)。 在JDK7中已经做了很多工作。


更新:

我进行了两个快速testing:

1)没有预热:

在这个testing中,我没有多次运行这些方法来“热身”JVM。 这意味着Java Hotspot Server Compiler没有机会优化代码,例如通过消除转义对象的locking。

 JDK 1.4.2_19 1.5.0_21 1.6.0_21 1.7.0_06 WITH-SYNC (ms) 3172 1108 3822 2786 WITHOUT-SYNC (ms) 3660 801 509 763 STRINGBUILDER (ms) N/A 450 434 475 

使用JDK 1.4,具有外部同步块的代码更快。 但是,使用JDK 5和更高版本的代码无需外部同步。

2)有预热:

在这个testing中,这些方法在计算时间之前运行了几次。 这样做是为了让JVM通过执行转义分析来优化代码。

 JDK 1.4.2_19 1.5.0_21 1.6.0_21 1.7.0_06 WITH-SYNC (ms) 3190 614 565 587 WITHOUT-SYNC (ms) 3593 779 563 610 STRINGBUILDER (ms) N/A 450 434 475 

再一次,在JDK 1.4中,带有外部同步块的代码更快。 但是,使用JDK 5及更高版本时,这两种方法的performance都一样好。

这是我的testing课(随意改进):

 public class StringBufferTest { public static void unsync() { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 9999999; i++) { buffer.append(i); buffer.delete(0, buffer.length() - 1); } } public static void sync() { StringBuffer buffer = new StringBuffer(); synchronized (buffer) { for (int i = 0; i < 9999999; i++) { buffer.append(i); buffer.delete(0, buffer.length() - 1); } } } public static void sb() { StringBuilder buffer = new StringBuilder(); synchronized (buffer) { for (int i = 0; i < 9999999; i++) { buffer.append(i); buffer.delete(0, buffer.length() - 1); } } } public static void main(String[] args) { System.out.println(System.getProperty("java.version")); // warm up for(int i = 0 ; i < 10 ; i++){ unsync(); sync(); sb(); } long start = System.currentTimeMillis(); unsync(); long end = System.currentTimeMillis(); long duration = end - start; System.out.println("Unsync: " + duration); start = System.currentTimeMillis(); sync(); end = System.currentTimeMillis(); duration = end - start; System.out.println("sync: " + duration); start = System.currentTimeMillis(); sb(); end = System.currentTimeMillis(); duration = end - start; System.out.println("sb: " + duration); } } 

缺乏经验,无能,或者更可能是重构后仍然存在的死而无益的代码。

您对这个问题的价值提出质疑 – 现代编译器将使用转义分析来确定所讨论的对象不能被另一个线程引用,因此将一并删除(删除)同步。

(从更广泛的意义上讲,在局部variables上同步有时是有用的 – 毕竟它们仍然是对象,而另一个线程仍然可以引用它们(只要它们在被创build后以某种方式“发布”)。尽pipe如此,这还不是一个好主意,因为它往往不明确,而且很难得到正确的结果 – 在这些情况下,与其他线程更明确的locking机制在整体上可能会更好。

我不认为同步可以产生任何效果,因为在超出范围之前, buffer永远不会传递给另一个方法或存储在一个字段中,所以没有其他线程可以访问它。

其原因可能是政治性的 – 我也遇到过类似的情况:一个“尖尖的老板”坚持用setter方法克隆一个string,而不是仅仅存储引用,因为害怕内容改变。他并不否认string是不可改变的,但坚持克隆它以防万一。由于它是无害的(就像这个同步),我没有争辩。

这有点疯狂…除了增加开销之外什么也不做。 更不用说,对StringBuffer的调用已经同步了,这就是为什么StringBuilder是你不会有多个线程访问同一个实例的情况下的首选。

国际海事组织,这是没有必要同步该局部variables。 只有当它暴露给其他人时,例如通过将其传递给一个函数来存储它,并有可能在另一个线程中使用它,同步才是有意义的。
但是,事实并非如此,我没有看到它的用处