当前位置: 首页 > news >正文

synchronized 锁字符串:常见坑点与解决策略

文章目录

    • 问题分析
      • 字符串常量池引发的锁复用问题
      • 字符串的不可变性
      • 使用intern() 方法以及对应的性能问题
        • 为什么不能将所有字符串都放入常量池?
          • 问题1:频繁的 Full GC
          • 问题2:性能下降
    • 使用建议
      • 不要用字符串作为锁对象
      • 使用 Google Guava 提供的 Interner 类

问题分析

在 Java 中,synchronized 关键字常被用于解决并发问题,确保线程安全。然而,当我们锁定的是字符串时,会引发一些意想不到的问题。这里分析synchronized 锁字符串相关问题点和优化方案整理。

字符串常量池引发的锁复用问题

在 JVM 中,字符串常量池是一个特殊的内存区域,用于存储字符串字面量。当两个字符串字面量相同时,它们实际上会引用常量池中的同一个对象。这意味着,如果你在不同的类或代码片段中使用了相同的字符串来作为锁对象,可能无意中锁定了同一个对象,导致跨代码段的竞争问题。

public class Test1 {private String lock1 = "lock";public void method() {synchronized (lock1) {System.out.println("Thread 1 ");}}
}public class Test2 {private String lock2 = "lock";public void method() {synchronized (lock2) {System.out.println("Thread 2 ");}}
}

问题案例:
假设系统多个流程中使用synchronized使用字符串锁,锁的信息可能是手机号,用户ID,姓名,账号等等,可能会出现相同冲突的问题,那么这种就有一定的几率出现跨代码段的竞争问题。

字符串的不可变性

字符串是不可变对象,这意味着一旦创建,字符串对象就无法被修改。当我们使用字符串作为锁时,如果在代码中不小心修改了这个字符串(例如使用 + 操作符拼接字符串),锁对象就会被改变,从而导致 synchronized 失效,线程安全性得不到保障。

使用intern() 方法以及对应的性能问题

日常中如果通过锁字符串对象的方式是锁不住字符串。因此字符串对象不是同一个地址,因此如果想要锁住字符串,需要把字符串对象添加到字符串常量池中。如果通过XXX user = XXX()的方式锁user.getUserId()是无法有效锁住的。
intern() 是 String 类中的一个方法,用于将字符串放入常量池中。具体来说,intern() 方法会检查当前字符串在常量池中是否存在。如果存在,则返回常量池中该字符串的引用;如果不存在,则将该字符串放入常量池中,并返回其引用。
使用synchronized 锁字符串,需要将字符串添加到字符串常量池中。日常使用中通过通过new对象的方式创建对象,再取对象的字段,因此需要使用intern把字符串放入常量池中,但是直接使用String的intern全部把字符串放入常量池会存在一些问题。显然在数据量很大的情况下,将所有字符串都放入常量池是不合理的,常量池大小依赖服务器内存,且只有等待fullGC,极端情况下会导致频繁fullGC。并且在数据量很大的情况下,将字符串放入常量是存在性能问题。

为什么不能将所有字符串都放入常量池?

Java 中的字符串常量池是一个专门用于存储字符串字面量的内存区域,常量池能够减少重复字符串的内存占用,提升性能。然而,常量池的大小是有限的,并且受到服务器内存的限制。将大量动态生成的字符串都放入常量池可能会带来以下几个问题:

问题1:频繁的 Full GC

常量池是 JVM 堆内存的一部分,堆内存有限。当常量池中存储了大量的字符串,且达到堆内存的上限时,JVM 可能需要进行 Full GC 来回收内存。Full GC 是一种相对耗时的操作,特别是在大数据量的场景下,它会导致应用停顿时间变长,影响系统性能。如果每次 Full GC 后,常量池内存依然不足以存储新字符串,那么 Full GC 可能会变得频繁,从而影响系统的整体稳定性和响应速度。

问题2:性能下降

intern() 方法会在常量池中查找或插入字符串对象。当字符串数据量非常大时,常量池的查找操作需要消耗一定的时间。在高并发和大数据量的场景下,这种查找操作的开销可能会变得非常明显,进而影响系统的性能表现。

使用建议

不要用字符串作为锁对象

为了避免上述问题,最佳实践是不要使用字符串作为锁对象。可以选择使用其他不可变且唯一的对象作为锁,例如 Object 或者定义一个专门的锁对象。

private final Object lock = new Object();public void method() {synchronized (lock) {System.out.println("Thread 1");}
}

使用 Google Guava 提供的 Interner 类

Guava 的 Interner 是一个接口,它的主要功能是确保相同的对象引用同一个实例。这类似于 String.intern()的功能,但更加灵活,并且不会将对象放入 JVM 的字符串常量池中,而是使用自己管理的池来存储对象。Guava 提供了两种 Interner 的实现:
• Interners.newWeakInterner():使用弱引用来存储对象,允许 GC 在内存紧张时回收这些对象。
• Interners.newStrongInterner():使用强引用来存储对象,直到这些对象不再被引用时才会被回收。

假设我们在应用中需要锁定大量动态生成的字符串,如果直接使用 String.intern() 会导致频繁的 Full GC 和性能问题,此时我们可以使用 Guava 的 Interner 类来替代。

import com.google.common.collect.Interner;
import com.google.common.collect.Interners;public class InternerExample {// 使用 Interner 来管理锁对象private final Interner<String> stringInterner = Interners.newWeakInterner();public void method(String key) {// 将字符串放入 Interner,确保相同字符串共享同一个实例String internedKey = stringInterner.intern(key);synchronized (internedKey) {// 执行同步代码块System.out.println("Locked on: " + internedKey);}}
}

使用 Interner 类替代 String.intern() 具有以下几个优点:

  1. 避免 JVM 字符串常量池的限制
    String.intern() 会将字符串放入 JVM 的字符串常量池,常量池的大小受限于堆内存,而且频繁操作可能导致 Full GC。使用 Interner,我们可以避免这些问题,因为 Interner 使用的是自己管理的对象池,池的大小和内存管理都由 Guava 来处理,不依赖于 JVM 的字符串常量池。
  2. 提供灵活的内存管理
    通过 Interners.newWeakInterner(),可以使用弱引用来管理池中的对象。这样,当 JVM 内存紧张时,GC 可以回收这些不再使用的对象,避免了堆内存过度占用的问题。

http://www.mrgr.cn/news/54128.html

相关文章:

  • MyBatis入门程序之客户添加、更新与删除
  • 【Git】远程操作-标签管理-多人协作
  • VideoCLIP-XL:推进视频CLIP模型对长描述的理解
  • 查看SQL执行计划 explain
  • Java基础12-特殊文件和日志技术
  • 2.6.ReactOS系统中从内核中发起系统调用
  • python-代码技巧
  • Redis可视化软件安装
  • Leecode刷题之路第25天之K个一组翻转链表
  • CSS 设置网页的背景图片
  • StarTowerChain:开启去中心化创新篇章
  • taro底部导航,Tabbar
  • 电能表预付费系统-标准传输规范(STS)(13)
  • 【str_replace替换导致的绕过】
  • 解决因内存过小芯片使用malloc造成内存碎片使程序偶发性卡死问题
  • mysql 10 单表访问方法
  • Java 数据基本类型详解(各基本数据类型及其大小、数据类型转换、数据溢出问题、自动装箱与拆箱的影响)
  • 架构师之路-学渣到学霸历程-23
  • 理解C#中空值条件运算符及空值检查简化
  • 十五、Python基础语法(list(列表)-上)
  • AI写作助手系统盈利模式分析:打造盈利的AI网站
  • 可能要招1000+应届生!直击美团心动岗位 - 美团面试原题 - 贪心算法题如何用 go 和 C++ 解决
  • 【CSAPP】【答案/解析】《深入理解计算机系统》实验一/datalab-handout实验
  • 记录迷茫!
  • 【运维基础知识】《Linux 系统架构与文件系统及权限管理全解析》
  • java反射介绍