java - 局部变量同步

标签 java multithreading

今天遇到了org.jasig.cas.client.util.CommonUtilsconstructServiceUrl()方法类(class)。我觉得他很奇怪:

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

作者为什么要同步一个局部变量?

最佳答案

这是手动“lock coarsening”的示例,可能已经完成以提高性能。

考虑这两个片段:

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 是同步方法),而在第二种情况下,锁只被获取和释放一次。这可以提高性能,这可能是作者这样做的原因。在某些情况下,编译器可以执行 lock coarsening对你来说(但不是围绕循环结构,因为你最终可能会长时间持有锁)。

顺便说一句,编译器可以检测到对象没有从方法中“转义”,因此完全删除了对对象的获取和释放锁(锁省略),因为无论如何其他线程都无法访问该对象。在 JDK7 中已经做了很多工作。 .


更新:

我进行了两个快速测试:

1) 没有热身:

在这个测试中,我没有多次运行这些方法来“预热”JVM。这意味着 Java 热点服务器编译器没有机会优化代码,例如通过消除逃逸对象的锁。

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,带有外部同步块(synchronized block)的代码更快。但是,使用 JDK 5 及更高版本的代码没有外部同步会胜出。

2) 热身:

在这个测试中,这些方法在计算时间之前运行了几次。这样做是为了让 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,带有外部同步块(synchronized block)的代码更快。但是,对于 JDK 5 及更高版本,这两种方法的性能同样出色。

这是我的测试课(欢迎改进):

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

关于java - 局部变量同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6123024/

相关文章:

java - 检查数组长度是否是完全平方数

javascript - 事件驱动编程node.js?

java - 同步对ArrayList的访问

c# - 如何创建一个连续循环的线程/任务?

c - 寻找线程同步性能问题的解释

c++ - 在将成员函数作为函数变量传递时无效使用非静态成员函数 C++

java - 使用 if 在父类(super class)型和子类型之间进行选择

java - 我们应该明确地杀死一个线程吗?

java - JavaFX 在不同屏幕上的不同尺寸

java - Java 将子数组复制到已初始化数组的方法