基于String实现同步锁的方法步骤
在某些时候,我们可能想基于字符串做一些事情,比如:针对同一用户的并发同步操作,使用锁字符串的方式实现比较合理。因为只有在相同字符串的情况下,并发操作才是不被允许的。而如果我们不分青红皂白直接全部加锁,那么整体性能就下降得厉害了。
因为string的多样性,看起来string锁是天然比分段锁之类的高级锁更有优势呢。
因为String类型的变量赋值是这样的:Stringa="helloworld.";所有往往会有个错误的映象,String对象就是不可变的。
额,关于这个问题的争论咱们就不细说了,总之,"a"!="a"是有可能成立的。
另外,针对上锁这件事,我们都知道,锁是要针对同一个对象,才会有意义。所以,粗略的,我们可以这样使用字符串锁:
publicvoidmethod1(){
Stringstr1="a";
synchronized(str1){
//dosyncathings...
}
}
publicvoidmethod2(){
Stringstr2="a";
synchronized(str2){
//dosyncbthings...
}
}
乍一看,这的确很方便简单。但是,前面说了,"a"是可能不等于"a"的(这是大部分情况,只有当String被存储在常量池中时值相同的String变量才相等)。
所以,我们可以稍微优化下:
publicvoidmethod3(){
Stringstr1="a";
synchronized(str1.intern()){
//dosyncathings...
}
}
publicvoidmethod4(){
Stringstr2="a";
synchronized(str2.intern()){
//dosyncbthings...
}
}
看起来还是很方便简单的,其原理就是把String对象放到常量池中。但是会有个问题,这些常量池的数据如何清理呢?
不管怎么样,我们是不是可以自己去基于String实现一个锁呢?
肯定是可以的了!直接上代码!
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importjava.util.concurrent.ConcurrentHashMap;
importjava.util.concurrent.ConcurrentMap;
importjava.util.concurrent.CountDownLatch;
/**
*基于string的锁实现
*/
publicfinalclassStringBasedMutexLock{
privatestaticfinalLoggerlogger=LoggerFactory.getLogger(StringBasedMutexLock.class);
/**
*字符锁管理器,将每个字符串转换为一个CountDownLatch
*
*即锁只会发生在真正有并发更新同一个String的情况下
*
*/
privatestaticfinalConcurrentMaplockKeyHolder=newConcurrentHashMap<>();
/**
*基于lockKey上锁,同步执行
*
*@paramlockKey字符锁
*/
publicstaticvoidlock(StringlockKey){
while(!tryLock(lockKey)){
try{
logger.debug("【字符锁】并发更新锁升级,{}",lockKey);
blockOnSecondLevelLock(lockKey);
}catch(InterruptedExceptione){
Thread.currentThread().interrupt();
logger.error("【字符锁】中断异常:"+lockKey,e);
break;
}
}
}
/**
*释放lockKey对应的锁选项,使其他线程可执行
*
*@paramlockKey要使用互斥的字符串
*@returntrue:释放成功,false:释放失败,可能被其他线程误释放
*/
publicstaticbooleanunlock(StringlockKey){
//先删除锁,再释放锁,此处会导致后续进来的并发优先执行,无影响
CountDownLatchrealLock=getAndReleaseLock1(lockKey);
releaseSecondLevelLock(realLock);
returntrue;
}
/**
*尝试给指定字符串上锁
*
*@paramlockKey要使用互斥的字符串
*@returntrue:上锁成功,false:上锁失败
*/
privatestaticbooleantryLock(StringlockKey){
//此处会导致大量ReentrantLock对象创建吗?
//其实不会的,这个数量最大等于外部并发数,只是对gc不太友好,会反复创建反复销毁y
returnlockKeyHolder.putIfAbsent(lockKey,newCountDownLatch(1))==null;
}
/**
*释放1级锁(删除)并返回重量级锁
*
*@paramlockKey字符锁
*@return真正的锁
*/
privatestaticCountDownLatchgetAndReleaseLock1(StringlockKey){
returnlockKeyHolder.remove(lockKey);
}
/**
*二级锁锁定(锁升级)
*
*@paramlockKey锁字符串
*@throwsInterruptedException中断时抛出异常
*/
privatestaticvoidblockOnSecondLevelLock(StringlockKey)throwsInterruptedException{
CountDownLatchrealLock=getRealLockByKey(lockKey);
//为null说明此时锁已被删除,nextrace
if(realLock!=null){
realLock.await();
}
}
/**
*二级锁解锁(如有必要)
*
*@paramrealLock锁实例
*/
privatestaticvoidreleaseSecondLevelLock(CountDownLatchrealLock){
realLock.countDown();
}
/**
*通过key获取对应的锁实例
*
*@paramlockKey字符串锁
*@return锁实例
*/
privatestaticCountDownLatchgetRealLockByKey(StringlockKey){
returnlockKeyHolder.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:正常无运行
*/
privatetransientvolatileAtomicIntegerrunningStatus=newAtomicInteger(0);
//更新时先获取该状态:
publicvoidmethod5(){
AtomicIntegerrunningStatus=link.getRunningStatus();
//正在删除数据过程中,则等待
if(!runningStatus.compareAndSet(0,1)){
//1.等待另外线程删除完成
//2.删除正在更新标识
//3.重新运行本次数据放入逻辑
longlockStartTime=System.currentTimeMillis();
longmaxLockTime=10*1000;
while(!runningStatus.compareAndSet(0,1)){
if(System.currentTimeMillis()-lockStartTime>maxLockTime){
break;
}
}
runningStatus.compareAndSet(1,0);
thrownewRuntimeException("数据正在更新,重新运行:"+link.getLinkKey()+link);
}
try{
//dosyncthings
}
finally{
runningStatus.compareAndSet(1,0);
}
}
publicvoidmethod6(){
AtomicIntegerrunningStatus=link.getRunningStatus();
if(!runningStatus.compareAndSet(0,4)){
logger.error("数据正在更新中,不得删除,返回");
return;
}
try{
//dosyncthings
}
catch(Exceptione){
logger.error("并发更新异常:",e);
}
finally{
runningStatus.compareAndSet(4,0);
}
}
实际测试下来,CAS性能是要比synchronized之类的锁性能要好的。当然,我们这里针对的并发数都是极少的,我们只是想要保证这极少情况下的线程安全性。所以,其实也还好。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。