为什么同步锁不适用于字符串连接的原因

Why is reason that synchronized lock not working on String concatenation

我正在使用以下代码。我无法实现同步。按照我的说法,字符串池概念应该在这里起作用。

我想知道问题背后的原因,而不是替代方案。

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SynchronizationBug {
    private static Map<String,Date> concurrentMap=new ConcurrentHashMap();
    
    public static void start(String processId) throws InterruptedException{
        final String lock="log"+processId;
        synchronized (lock) {
            if(concurrentMap.containsKey(processId)) {
                System.out.println("Process is already working and started at "+concurrentMap.get(processId));
                return;
            }       
            concurrentMap.put(processId,new Date());
        }
    }
    
    
    public static void main(String[] args) throws InterruptedException {
//      System.out.println(isPrime(10));
        final String pId="p1";
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    start(pId);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        
        Thread t2=new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    start(pId);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        for (String s:concurrentMap.keySet()) {
            System.out.println(s+" - "+concurrentMap.get(s));
        }
    }
    
}

如果我锁定作为方法参数传递的 processId,那么一切都会按预期进行。 Java 这种行为的原因是什么?我已经在 Java8 Eclipse IDE.

上测试过了

编辑: 如果字符串池概念没有相关性,那么下面的代码如何工作。

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SynchronizationBug {
    private static Map<String,Date> concurrentMap=new ConcurrentHashMap();
    
    public static void start(String processId) throws InterruptedException{
        final String lock="log"+processId;
        synchronized (processId) {
            if(concurrentMap.containsKey(processId)) {
                System.out.println("Process is already working and started at "+concurrentMap.get(processId));
                return;
            }       
            concurrentMap.put(processId,new Date());
        }
    }
    
    
    public static void main(String[] args) throws InterruptedException {
//      System.out.println(isPrime(10));
        final String pId1="p1";
        final String pId2="p1";
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    start(pId1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        
        Thread t2=new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    start(pId2);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        for (String s:concurrentMap.keySet()) {
            System.out.println(s+" - "+concurrentMap.get(s));
        }
    }
    
}

在这里,我将两个具有相同 value.Due 的不同字符串对象传递给它们在池中的相同引用,锁定工作正常。 请指正....

此行为的原因是,如果操作数不是两个编译时常量表达式,则字符串连接会导致创建字符串的新实例。见 here in the language spec:

The String object is newly created (§12.5) unless the expression is a constant expression (§15.29).

方法参数不是编译时常量表达式,因为可以使用参数的任何值调用该方法。

而且,因为当前线程是唯一可以访问这个新创建的字符串的线程,所以对其进行同步没有任何效果。

既然你说你不想知道其他选择,那我就到此为止吧。


尽管我指出了规范的相关部分,但您似乎不相信您要同步的字符串是不同的。尝试在 synchronized:

之前添加这一行
    System.out.println(System.identityHashCode(lock));  // Add this
    synchronized (lock) {
      // ...

这将打印出锁的“身份哈希码”,它与 lock.hashCode() 不同,后者基于字符串的值。这将显示 synchronized 正在同步不同的值(除非你是 异常 lucky/unlucky,因为哈希冲突不太可能但可能发生)。