https://www.modb.pro/db/533660

前言

在日常项目开发中为了解决用户数据并发操作的问题,需要对代码块进行加锁保护。

例如:用户输入存数据库,重复数据不存DB;用户操作缓存数据等,这里想尽可能把锁的对象放小,因此通常都是锁用户而不是锁整个类或者代码块;然而在用synchronized(userId) 的时候可能会存在一些问题。

synchronized 锁字符串的问题

使用synchronized锁一个字符串👇

package thread;

/**
* @author: 邹祥发
* @date: 2022/8/22 11:52
*/
public class ThreadTest implements Runnable {
   @Override
   public void run() {
       synchronized (new String("字符串常量")) {
           //线程进入
           System.out.println(" thread start");
           try {
               //进入后睡眠
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           //线程结束
           System.out.println(" thread end");
      }
  }

   public static void main(String[] args) {
       for (int i = 0; i < 3; i++) {
           Thread thread = new Thread(new ThreadTest(), "dd");
           thread.start();
      }
  }
}

运行结果如下:

thread start
thread start
thread start
thread end
thread end
thread end

可以发现还是并发执行了,因为synchronized (new String("字符串常量"))锁的对象不是同一个,仅仅是值相等,此时的字符串是在堆栈中。将代码修改为如下

@Override
public void run() {
   String str = "字符串常量";
   synchronized (str) {
       //线程进入
       System.out.println(" thread start");
       try {
           //进入后睡眠
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       //线程结束
      System.out.println(" thread end");
  }
}

或者在**修改锁的内容为synchronized (new String("字符串常量").intern()) **,得到运行结果为

thread start
thread end
thread start
thread end
thread start
thread end

通过上面结果可以看出此时synchronized 是可以锁住字符串了,但是如果通过锁字符串对象的方式是锁不住字符串的。因为字符串对象不是同一个地址,因此如果想要锁住用户ID,需要把用户ID添加到字符串常量池中。如果通过User user = new User()的方式锁user.getUserId()是无法有效锁住用户的

看下下面的例子

public static void main(String[] args) {
   String name = "xiaozou";
   String nameObj = new String("xiaozou");
   System.out.println(name.equals(nameObj));
   System.out.println(name == nameObj);
   System.out.println(name == nameObj.intern());
}

运行结果为

true
false
true

通过上面的结果可以看出,name字符串常量和nameObj字符串对象的值相等,地址不同。通过new的对象是在堆栈中,字符串常量是存放在常量池中,通过nameObj.intern()把字符串对象放入常量池中,则地址是同一个