自定义分段锁的实现

内容纲要

业务背景

  • 在记账软件中,每个用户的统计信息需要存放与redis中,每一次修改或者新增数据后都需要对redis进行更新操作,更新的话需要先获取到redis中的值,然后在对redis进行更新,由于两个操作并不是原子操作,所以需要在java代码中进行锁的操作才能实现

全局锁

  • 使用全局锁的话则需要在方法之上添加synchronized关键字或者使用Lock类进行加锁操作
  • 使用全局锁之后,那么在jvm中只有一个线程能够对当前方法进行操作,这样的话就影响性能以及吞吐量,如果能够对单独的一个用户进行加锁的话,这样并不会影响到redis中的计算结果,并且可以多个用户进行同时的计算,提高系统的吞吐量

分段锁

  • 分段锁的实现则是对每个用户进行单独的加锁操作, 具体实现原理则是使用synchronized关键词可以指定具体的锁对象,synchronized关键词的锁必须为final对象,那么在jvm中String对象则满足对象的条件,只需要将用户id转为String类型,并添加到一个缓存中,每次加锁只需要获取到对应的用户即可,具体代码如下
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Clay
 * @date 2023-09-06
 */
public class SegmentLock {

    //做好用户Id Long类型到String类型的缓存,String类型为final类型,所以用户id唯一的情况下锁也是唯一的
    private final Map<Long, String> userLock = new ConcurrentHashMap<>();

    /**
     * 分段锁的具体实现
     * @param task 在分段锁中需要执行的方法
     */
    public void segmentLock(Task task) {
        //从Spring Security中获取到当前用户的id信息
        Long userId = SecurityUtils.getUserId();
        //使用双重锁检查机制检查用户锁中是否已经存在当前用户的String类型
        if (!userLock.containsKey(userId)) {
            synchronized (userLock) {
                if (!userLock.containsKey(userId)) {
                    userLock.put(userId, userId.toString());
                }
            }
        }
        //使用当前用户的String类型作为锁参数进行加锁操作
        synchronized (userLock.get(userId)) {
            task.run();
        }
    }

    /**
     * 任务执行类
     */
    public interface Task {
        void run();
    }
}

其他扩展

  • 在只需要对某一个维度下的数据进行操作的时候,其他维度的数据操作并不会对当前维度的数据造成任何影响时,则可以使用此维度的id作为分段锁的唯一标识,从而实现分段锁
THE END
分享
二维码
< <上一篇
下一篇>>