如何基于 String 实现同步锁?

Synchronized 同步锁详解 Synchronized是JAVA的关键字,是基于JVM层面的独占式的悲观锁,同时也属于可重入锁经常来用于解决多个线程之间访问访问资源的同步性,保证在同一时刻只有一个线程访问这个资源。 阅读详情

如何基于String实现同步锁?

在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。

因为只有在相同字符串的情况下,并发操作才是不被允许的。而如果我们不分青红皂白直接全部加锁,那么整体性能就下降得厉害了。

因为String的多样性,看起来string锁是天然比分段锁之类的高级锁更有优势呢。

因为String 类型的变量赋值是这样的: String a = "hello world."; 所有往往会有个错误的映象,String对象就是不可变的。

额,关于这个问题的争论咱们就不细说了,总之, "a" != "a" 是有可能成立的。

另外,针对上锁这件事,我们都知道,锁是要针对同一个对象,才会有意义。所以,粗略的,我们可以这样使用字符串锁:

public void method1() {  
    String str1 = "a";  
    synchronized (str1) {  
        // do sync a things...  
    }  
}  

public void method2() {  
    String str2 = "a";  
    synchronized (str2) {  
        // do sync b things...  
    }  
}  

乍一看,这的确很方便简单。但是,前面说了, "a" 是可能不等于 "a" 的(这是大部分情况,只有当String被存储在常量池中时值相同的String变量才相等)。5个刁钻的String面试题,推荐大家看下。

所以,我们可以稍微优化下:

public void method3() {  
    String str1 = "a";  
    synchronized (str1.intern()) {  
        // do sync a things...  
    }  
}  

public void method4() {  
    String str2 = "a";  
    synchronized (str2.intern()) {  
        // do sync b things...  
    }  
}  

看起来还是很方便简单的,其原理就是把String对象放到常量池中。但是会有个问题,这些常量池的数据如何清理呢?

不管怎么样,我们是不是可以自己去基于String实现一个锁呢?关注微信公众号:Java技术栈,在后台回复:Java,可以获取我整理的 N 篇 Java 教程,都是干货。

肯定是可以的了!直接上代码!

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

import java.util.concurrent.ConcurrentHashMap;  
import java.util.concurrent.ConcurrentMap;  
import java.util.concurrent.CountDownLatch;  

/**  
 * 基于string 的锁实现  
 */  
public final class StringBasedMutexLock {  

    private static final Logger logger = LoggerFactory.getLogger(StringBasedMutexLock.class);  

    /**  
     * 字符锁 管理器, 将每个字符串 转换为一个 CountDownLatch  
     *  
     *      即锁只会发生在真正有并发更新 同一个 String 的情况下  
     *  
     */  
    private static final ConcurrentMap<String, CountDownLatch> lockKeyHolder = new ConcurrentHashMap<>();  

    /**  
     * 基于lockKey 上锁,同步执行  
     *  
     * @param lockKey 字符锁  
     */  
    public static void lock(String lockKey) {  
        while (!tryLock(lockKey)) {  
            try {  
                logger.debug("【字符锁】并发更新锁升级, {}", lockKey);  
                blockOnSecondLevelLock(lockKey);  
            } catch (InterruptedException e) {  
                Thread.currentThread().interrupt();  
                logger.error("【字符锁】中断异常:" + lockKey, e);  
                break;  
            }  
        }  
    }  

    /**  
     * 释放 lockKey 对应的锁选项,使其他线程可执行  
     *  
     * @param lockKey 要使用互斥的字符串  
     * @return true: 释放成功, false: 释放失败,可能被其他线程误释放  
     */  
    public static boolean unlock(String lockKey) {  
        // 先删除锁,再释放锁,此处会导致后续进来的并发优先执行,无影响  
        CountDownLatch realLock = getAndReleaseLock1(lockKey);  
        releaseSecondLevelLock(realLock);  
        return true;  
    }  

    /**  
     * 尝试给指定字符串上锁  
     *  
     * @param lockKey 要使用互斥的字符串  
     * @return true: 上锁成功, false: 上锁失败  
     */  
    private static boolean tryLock(String lockKey) {  
        // 此处会导致大量 ReentrantLock 对象创建吗?  
        // 其实不会的,这个数量最大等于外部并发数,只是对 gc 不太友好,会反复创建反复销毁y  
        return lockKeyHolder.putIfAbsent(lockKey, new CountDownLatch(1)) == null;  
    }  

    /**  
     * 释放1级锁(删除) 并返回重量级锁  
     *  
     * @param lockKey 字符锁  
     * @return 真正的锁  
     */  
    private static CountDownLatch getAndReleaseLock1(String lockKey) {  
        return lockKeyHolder.remove(lockKey);  
    }  

    /**  
     * 二级锁锁定(锁升级)  
     *  
     * @param lockKey 锁字符串  
     * @throws InterruptedException 中断时抛出异常  
     */  
    private static void blockOnSecondLevelLock(String lockKey) throws InterruptedException {  
        CountDownLatch realLock = getRealLockByKey(lockKey);  
        // 为 null 说明此时锁已被删除,  next race  
        if(realLock != null) {  
            realLock.await();  
        }  
    }  

    /**  
     * 二级锁解锁(如有必要)  
     *  
     * @param realLock 锁实例  
     */  
    private static void releaseSecondLevelLock(CountDownLatch realLock) {  
        realLock.countDown();  
    }  

    /**  
     * 通过key 获取对应的锁实例  
     *  
     * @param lockKey 字符串锁  
     * @return 锁实例  
     */  
    private static CountDownLatch getRealLockByKey(String lockKey) {  
        return lockKeyHolder.get(lockKey);  
    }  

}  

使用时,只需传入 lockKey 即可。 
这样做有什么好处吗?

  1. 使用ConcurrentHashMap实现锁获取,性能还是不错的;

  2. 每个字符串对应一个锁,使用完成后就删除,不会导致内存溢出问题;

  3. 可以作为一个外部工具使用,业务代码接入方便,无需像 synchronized 一样,需要整段代码包裹起来;

  4. 本文只是想展示实现 String 锁,此锁并不适用于分布式场景下的并发处理;

扩展: 如果不使用 String 做锁,如何保证大并发前提下的小概率并发场景的线程安全?

我们知道 CAS 的效率是比较高的,我们可以使用原子类来进行CAS的操作。

比如,我们添加一状态字段, 操作此字段以保证线程安全:

/**  
 * 运行状态  
 * 4: 正在删除, 1: 正在放入队列中, 0: 正常无运行  
 */  
private transient volatile AtomicInteger runningStatus = new AtomicInteger(0);  


// 更新时先获取该状态:  
public void method5() {  
    AtomicInteger runningStatus = link.getRunningStatus();  
    // 正在删除数据过程中,则等待  
    if(!runningStatus.compareAndSet(0, 1)) {  
        // 1. 等待另外线程删除完成  
        // 2. 删除正在更新标识  
        // 3. 重新运行本次数据放入逻辑  
        long lockStartTime = System.currentTimeMillis();  
        long maxLockTime = 10 * 1000;  
        while (!runningStatus.compareAndSet(0, 1)) {  
            if(System.currentTimeMillis() - lockStartTime > maxLockTime) {  
                break;  
            }  
        }  
        runningStatus.compareAndSet(1, 0);  
        throw new RuntimeException("数据正在更新,重新运行: " + link.getLinkKey() + link);  
    }  
    try {  
        // do sync things  
    }  
    finally {  
        runningStatus.compareAndSet(1, 0);  
    }  
}  

public void method6() {  
    AtomicInteger runningStatus = link.getRunningStatus();  
    if (!runningStatus.compareAndSet(0, 4)) {  
        logger.error(" 数据正在更新中,不得删除,返回 ");  
        return;  
    }  
    try {  
        // do sync things  
    }  
    catch (Exception e) {  
        logger.error("并发更新异常:", e);  
    }  
    finally {  
        runningStatus.compareAndSet(4, 0);  
    }  
}  

实际测试下来,CAS 性能是要比 synchronized 之类的锁性能要好的。

当然,我们这里针对的并发数都是极少的,我们只是想要保证这极少情况下的线程安全性。所以,其实也还好。

具身智能ACT模型在lerobot机械臂上的实践与优化 本文详细介绍了如何在低成本lerobot机械臂上实践与优化具身智能ACT模型。从环境搭建、数据采集到模型原理与代码实战,提供了完整的入门指南。重点分享了通过数据增强、混合开源数据集及模型微调等策略,有效提升模型在抓取放置任务中的泛化能力与鲁棒性,为机器人学习爱好者提供了宝贵的实战经验。 阅读详情

相关推荐

JMeter脚本录制-快速上手篇

有的项目应用场景下,压测时,需要考虑带宽因素,压测不只是压测接口,静态资源也要一起压测。一般来说,静态资源比较多,如果一个个手工去写请求,是比较繁琐的,此时,可以使用Jmeter的脚本录制功能。 步骤1,创建脚本录制模版。 JMeter菜单:文件>模版,下拉选择到Recording,点击“Create”按钮; 填写录制参数: hostToRecord:填写要录制的主机域名,例如,我们这里填入:www.baidu.com schemeToRecord:填写要录制的模式,例如,htt...

软件测试大叔 5734

Java之同步锁

Java支持同步锁(synchronization lock)机制以及可重入锁(re-entrant lock)机制,本章节主要描述同步锁的机制。

wys的专栏 1万+

别再死记硬背参数了!用ADS手把手教你仿真一个4GHz镜像抑制混频器(附完整工程文件)

本文详细解析了4GHz镜像抑制混频器的ADS仿真全流程,从理论到实践,涵盖混频器设计基础、ADS工程搭建、二极管选型、非线性仿真及性能优化。通过实战案例,帮助工程师掌握关键参数平衡与调试技巧,提升射频电路设计效率。

weixin_42552716的博客 339

synchronized同步锁原理详解

当对象获取到锁之后,由于某些资源并未准备完成,需要等待其他线程去准备资源,此时线程会通过wait()/notify()等方法进入等待/通知模式,在这种情况下线程释放锁之后会进入WaitSet,当其他线程准备好资源之后会通知WaitSet中等待的线程,WaitSet中的线程会进入到EntryList中,重新参与锁竞争。锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,只能从低到高升级,不会出现锁的降级。

qq_43648299的博客 2443

S7-1200 数据日志

数据日志文件按照标准 CSV 格式存储在 S7-1200 CPU 装载存储器或 S7-1200 SIMATIC 存储卡中。分别可通过 PLC Web 服务器或将数据文件传送到 PC 进行管理、查看。 一、测试目的: 下面以数据日志文件存储在 S7-1200 CPU 装载存储器中,使用PLC Web 服务器进行管理为例,实现下述功能: (1)执行“DataLogging”指令将产品型号“Type”,长度“Length”,宽度“Width”三个变量值写入到数据日志文件; (2)当写入的条数达到设定的3条

qq_43029096的博客 2543

路由三大分类:直连路由、静态路由、动态路由

直连路由、静态路由和动态路由各有其独特的特点和适用场景。直连路由适用于简单的直接连接网络,静态路由提供了精确的路径控制,而动态路由则在大型复杂网络中展现出强大的适应性和自动化能力。了解和掌握这三种路由类型的配置和管理,对于网络管理员来说,是确保网络高效、安全运行的重要技能。

网络技术联盟站 4699

Synchronized同步锁

有关synchronized同步锁的介绍!

qq_49194786的博客 1977

YOLO系列算法改进 | C3k2改进篇 | 融合MambaOut轻量化门控架构 | 纯卷积实现超越Mamba性能的同时兼顾极致轻量化与边缘部署效率 | CVPR 2025

YOLO系列算法改进 | C3k2改进篇 | 融合MambaOut轻量化门控架构 | 纯卷积实现超越Mamba性能的同时兼顾极致轻量化与边缘部署效率 | CVPR 2025 (以YOLOv26最新目标检测模型为基础,利用C3k2_MambaOut创新模块进行改进)

2502_91881744的博客 224

什么是同步锁?

同步锁(Synchronized Lock)是多线程编程中的一个关键概念,用来解决多个线程同时访问共享资源时可能引发的数据不一致问题。同步锁通过限制多个线程并发执行关键代码的能力,确保在同一时刻只有一个线程能够访问临界区(即共享资源的代码段),从而保证数据的一致性。我们可以通过 Java 的 synchronized 关键字实现同步锁。

小萌学习笔记 1499

12、有杆泵抽油系统:原理、性能与选择

本文全面介绍了有杆泵抽油系统的原理、性能分析和设备选择方法。有杆泵抽油系统广泛应用于石油开采领域,通过地面抽油装置与井下柱塞泵的配合,实现高效、稳定的原油提升。文章详细解析了系统的工作原理、组成结构、光杆运动特性、载荷计算、泵输送能力及功率需求,并提供了抽油装置的选择流程和实际案例分析。此外,还探讨了泵的容积效率影响因素、示功图的作用及系统优化运行的建议,为石油开采工程提供了理论支持和实践指导。

gan8painter的博客 435

【AUTOSAR-COM】-10.5-APP CAN Frame/PDU“发送周期不准“的影响因素

一个Tx PDU在COM模块中的数据流; Tx APP CAN Frame/PDU"发送周期不准"的影响因素;

「汽车电子助手」的博客 2390

用String实现同步锁,你会吗?

程序员的成长之路互联网/程序员/成长/职场关注阅读本文大概需要 4.2 分钟。来自:https://dwz.cn/mzLNZTIm如何基于String实现同步锁?在某些时候,我们可能想...

良月柒 1011

基于YOLO26深度学习框架的文档表格识别系统的算法研究实现方法

本文研究了基于YOLO26深度学习框架的文档表格识别系统实现方法。系统采用YOLO26网络架构,通过数据预处理、模型训练、检测推理和表格分析四个模块实现高效表格识别。研究详细阐述了数据归一化、增强等技术处理,以及采用迁移学习策略优化模型训练过程。实验表明,该系统能有效识别多种表格结构并生成结构化数据,为文档自动化处理提供了可靠解决方案。

一键难忘的博客 976

java 同步锁 key_如何基于String实现同步锁?

在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。因为只有在相同字符串的情况下,并发操作才是不被允许的。因为String 类型的变量赋值是这样的: String a = "hello world."; 所有往往会有个错误的映象,String对象就是不可变的。额,关于这个问题的争论咱们就不细说了,总之, "a" != "a" 是有可能成立的...

weixin_35462155的博客 387

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

如何基于String实现同步锁?

在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。因为只有在相同字符串的情况下,并发操作才是不被允许的。而如果我们不分青红皂白直接全部加锁,那么整体性能就下降得厉害了。   因为string的多样性,看起来string锁是天然比分段锁之类的高级锁更有优势呢。 因为String 类型的变量赋值是这样的: Strin...

weixin_30699741的博客 179

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

java 同步锁 key_基于String实现同步锁的方法步骤

在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。因为只有在相同字符串的情况下,并发操作才是不被允许的。而如果我们不分青红皂白直接全部加锁,那么整体性能就下降得厉害了。因为string的多样性,看起来string锁是天然比分段锁之类的高级锁更有优势呢。因为String 类型的变量赋值是这样的: String a = "hello worl...

weixin_34370877的博客 273

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

java锁string_java相关:基于String实现同步锁的方法步骤

java相关:基于String实现同步锁的方法步骤发布于 2020-12-12|复制链接摘记: 在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。因为只有在相同字符串的情况下,并发操作才是不被允许的。而如果我们不分青红皂白直接全部加锁,那么整体性能就下降得厉害了。因为string的多样性,看起来string锁是天然比分段锁之类的高级锁更有...

weixin_35753431的博客 690

Java中String做为synchronized同步锁使用详解

Java中String做为synchronized同步锁使用详解

weixin_45623983的博客 1572

使用String 来作为 同步锁减小锁的颗粒度,同时实现当前用户线程同步

// 控制重复提交非 并发重复提交 如果觉得有问题这个方法可以删除 public void repeatSubmission(OiePersonalHeaders header, BindingResult errors) { User user = UserInfoUtils.getCurrentUser(); StringBuffer sb = new StringBuffe...

taoriqiang10的博客 287
上一篇: 怎么对 Java 程序调优?不看别后悔!
下一篇: Java:如何更优雅的处理空值?
Java奥利给
博客等级 码龄6年 150粉丝 154原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值