【问题标题】:Why is reason that synchronized lock not working on String concatenation为什么同步锁不适用于字符串连接的原因
【发布时间】:2020-07-11 12:39:42
【问题描述】:

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

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

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的这种行为的原因是什么。我已经在 J​​ava8 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));
        }
    }
    
}

在这里,我传递了两个具有相同值的不同字符串对象。由于它们在池中的相同引用,锁定工作正常。 请纠正我....

【问题讨论】:

  • 您在哪里看到字符串池概念?事实上,每次调用 start 方法时,您显然是在不同的对象上进行同步。
  • “按照我的说法,字符串池的概念应该在这里起作用。” 你为什么这么认为?因为它没有。
  • “编辑”代码起作用的原因是您锁定了processId,而不是lock。这不是因为您传递了两个不同的字符串对象,因为 - 这实际上是因为字符串池 - pId1pId2 具有相同的值;这就是你要锁定的价值。

标签: java multithreading synchronization string-pool


【解决方案1】:

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

String 对象是新创建的(第 12.5 节),除非表达式是常量表达式(第 15.29 节)。

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

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

既然你说你不想知道替代方案,那我就停在那里。


尽管我指出了规范的相关部分,但您似乎不相信您正在同步的字符串是不同的。尝试在 synchronized 之前添加这一行:

    System.out.println(System.identityHashCode(lock));  // Add this
    synchronized (lock) {
      // ...

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-13
    • 2019-05-22
    • 2017-10-15
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 2019-03-06
    • 2021-03-09
    相关资源
    最近更新 更多