如何基于String实现同步锁?

  在某些时候,咱们可能想基于字符串作一些事情,好比:针对同一用户的并发同步操做,使用锁字符串的方式实现比较合理。由于只有在相同字符串的状况下,并发操做才是不被容许的。而若是咱们不分青红皂白直接所有加锁,那么总体性能就降低得厉害了。java

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

  确定是能够的了!直接上代码!线程

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

 

唠叨: 静下心来。

相关文章
相关标签/搜索