在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。因为只有在相同字符串的情况下,并发操作才是不被允许的。而如果我们不分青红皂白直接全部加锁,那么整体性能就下降得厉害了。
因为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<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 即可。
// 加锁 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 之类的锁性能要好的。当然,我们这里针对的并发数都是极少的,我们只是想要保证这极少情况下的线程安全性。所以,其实也还好。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对小牛知识库的支持。
本文向大家介绍不同js异步函数同步的实现方法,包括了不同js异步函数同步的实现方法的使用技巧和注意事项,需要的朋友参考一下 不同函数达到同步的函数模拟 funcList是函数执行函数的队列,其中回调函数中flag=true是同步标记量 以上这篇不同js异步函数同步的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持呐喊教程。
同步(Synchronize)操作是web编程中不可以避免的, 在java中, 我们通过同步代码块,同步方法,同步对象锁等等各种办法去实现,而且这些 方法都是java内置实现的我们直接使用就行。但是php中我们需要自己去实现。 Herosphp框架提供了2种实现同步操作的方法,供你在高并发中实现逻辑的有序操作。 FileSynLock FileSynLock 是基于系统的文件锁实现,它的特点是兼容
本文向大家介绍php基于协程实现异步的方法分析,包括了php基于协程实现异步的方法分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了php基于协程实现异步的方法。分享给大家供大家参考,具体如下: github上php的协程大部分是根据这篇文章实现的:http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-corou
问题内容: 我正在查看包含同步方法的第三方库中的一些代码,在此方法中,有一个锁定在实例变量上的同步块。与此类似: 这有意义吗?如果是这样,在同步方法中使用同步语句有什么好处? 鉴于同步方法锁定了整个对象,对我来说似乎是多余的。在使用非私有的实例变量时,这种方法是否有意义? 问题答案: 在您的示例中,该方法 同时 锁定了和的实例。其他方法可能仅锁定对象的实例 或 对象。 因此,是的,这完全取决于他们
请看下面给我带来麻烦的方法: 然后是run方法:
如果我们有一个synchronized方法,在使用同一个对象时需要进行同步,那该怎么做呢? 例如,我们有一个 同步函数,如果所有参数在不同的调用中都不同,则可以并行执行,但如果调用 1 中的参数与调用 2 中的参数相同,则不能并行执行: 如果和同时调用函数,我们将如何停止这里的死锁? 我认为和是易失性的,或者基于和的哈希值进行同步,或者通过包含和的新对象进行同步。