热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何基于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变量才相等)。

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


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实现一个锁呢?

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


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 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 即可。

// 加锁
StringBasedMutexLock.lock(linkKey);
// 解锁
StringBasedMutexLock.unlock(linkKey);

  这样做有什么好处吗?

    1. 使用ConcurrentHashMap实现锁获取,性能还是不错的;
    2. 每个字符串对应一个锁,使用完成后就删除,不会导致内存溢出问题;
    3. 可以作为一个外部工具使用,业务代码接入方便,无需像 synchronized 一样,需要整段代码包裹起来;

  不足之处?

    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 之类的锁性能要好的。当然,我们这里针对的并发数都是极少的,我们只是想要保证这极少情况下的线程安全性。所以,其实也还好。

 

唠叨: 静下心来。

 


不要害怕今日的苦,你要相信明天,更苦!
 
分类: java, 原理&故事, 源码, 并发&性能
标签: JAVA, 并发, 锁, String.intern, cas, ConcurrenHashMap

推荐阅读
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • andr ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • Java 类成员初始化顺序与数组创建
    本文探讨了Java中类成员的初始化顺序、静态引入、可变参数以及finalize方法的应用。通过具体的代码示例,详细解释了这些概念及其在实际编程中的使用。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
author-avatar
zcr2016
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有