ZHIYA
Blog

研究了一天终于设计了一个比较满意的java版的字符串ID锁

后台开发中经常碰到需要一次性查询修改某个用户数据的业务,这种情况最担心的就是前端因误操作或者网络问题导致业务接口调用多次,从而导致用户数据不一致而引起的用户投诉问题。

一般这种问题就是直接加锁就行了,但如果直接给业务接口加锁会导致多个不同的用户等待其中一个用户操作完,很显然和某个用户相关的数据的操作不应该影响其它用户的操作。

所以设计一个只针对用户ID的锁还是很有必要的,于是乎经过短时间的思考设计了第一版的ID锁。

public class IDLockHelper {
    static Logger logger = LoggerFactory.getLogger(IDLockHelper.class);

    ConcurrentHashMap<String, String> lockSet = new ConcurrentHashMap();

    static class SingleHolder {
        public static IDLockHelper instance = new IDLockHelper();
    }

    public static IDLockHelper getInstance() {
        return IDLockHelper.SingleHolder.instance;
    }

     public String getKeyLocker(String lockKey) {
        lockSet.putIfAbsent(lockKey, lockKey);
        return lockSet.get(lockKey);
    }
    
}


// 使用示例
Object getUserInfo(){
     synchronized(IDLockerHelper.getInstance().getKeyLocker("userid")){
        // 执行用户数据的查询和修改
     }
}

实现起来非常简单,一个ConcurrentHashMap存储用户ID, 然后每次需要执行一系列的用户数据查询和修改时,直接按上面的示例代码使用即可,非常方便。

但是随着用户量增加存储的用户ID数据越来越多,会容易引起OOM问题,虽然短时间没问题,但这种设计终究是个大隐患,所以经过一番调整和思考后,设计了一个可以自动删除的版本。

public class IDLockV2Helper {
    static Logger logger = LoggerFactory.getLogger(IDLockV2Helper.class);

    public static class LockItem {
        long lastLockTimeMs = 0;
    }

    ConcurrentHashMap<String, LockItem> lockSet = new ConcurrentHashMap();

    static class SingleHolder {
        public static IDLockV2Helper instance = new IDLockV2Helper();
    }

    public static IDLockV2Helper getInstance() {
        return IDLockV2Helper.SingleHolder.instance;
    }

    //    给this上锁。
    public synchronized Object getKeyLocker(String lockKey) {
        long curTimeMs = System.currentTimeMillis();
        LockItem tmpItem = new LockItem();
        tmpItem.lastLockTimeMs = curTimeMs;
        lockSet.putIfAbsent(lockKey, tmpItem);

        LockItem lockItem = lockSet.get(lockKey);
        lockItem.lastLockTimeMs = curTimeMs;
        clear();
        return lockItem;
    }

    //    删除超过一天的数据。
    void clear() {
        try {
            List<String> keyList = new ArrayList<>(lockSet.keySet());
            for (String key : keyList) {
                LockItem item = lockSet.get(key);
                if (item != null) {
                    if (item.lastLockTimeMs + 24 * 3600_000 <= System.currentTimeMillis()) {
                        lockSet.remove(key);
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.toString());
        }
    }
}

使用的时候和第一版一样。 这里是通过超时来删除锁的,毕竟在一天内达到能导致OOM的数据量的概率还是很小很小的。

版权声明:本文为ZHIYA网站的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:研究了一天终于设计了一个比较满意的java版的字符串ID锁