分布式锁的原理与实现 二、分布式锁的基础实现

本文介绍了基于Redis的分布式锁实现,包括接口定义、具体实现和测试方法。通过使用setnx命令设置分布式锁,并在获取锁失败时进行重试,确保了锁的公平性和并发控制。测试结果显示,分布式锁能有效协调多线程执行,避免资源竞争。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  这一节中,我们将会使用上一节中分布式锁的原理:分布式锁的原理与实现 一、分布式锁的原理 。来一步一步的实现分布式锁。

一、实现的接口定义:

  其中,我们先定义一个给外部使用的接口:

public interface DistributedLockService {

    boolean tryLock(Runnable runnable, String key);

    boolean tryLock(Runnable runnable, String key, int maxWaitTimeMills, int perWaitTimeMills);

}

  可以看到,上述定义了两个方法,其中:

    第一个方法是来根据key来执行runnable,如果拿到了分布式锁,那么将会执行runnable,并返回true,如果拿不到,不执行runnable,返回false。

    第二个方法也是根据key来执行runnable,但是会有一个等待的时间,其中 maxWaitTimeMills 为最高的等待时间, perWaitTimeMills 为拿不到分布式锁的时候,多少毫秒来重试一次。

  可能很多人对第二个方法有点疑问,是这样,当多个线程都在抢一个分布式锁的时候,比如A,B,C三个线程,同时时间只有一个能拿到,比如A线程,那么B,C线程拿不到,会等待 perWaitTimeMills 毫秒后,再去重试,知道从开始抢,到抢了n次之后,总时长超过了 maxWaitTimeMills 毫秒之后,会放弃,并返回false。


二、实现:

  首先我们是使用Redis来进行实现的,所以本地必须启动一个Redis,在项目中,我们使用Jedis来连接操作Redis。

  其中的原理请看上一篇的原理讲解:分布式锁的原理与实现 一、分布式锁的原理 。

  此项目的全部源码已经发布到github上,地址为 项目源码地址 ,欢迎大家star和提问题。

  具体的实现如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;

/**
 *
 * Created by happyheng on 2018/4/6.
 */
@Service
public class DistributedLockServiceImpl implements DistributedLockService {

    @Autowired
    private Jedis jedis;

    /**
     * 分布式锁的超时时间,此设置为10s
     */
    private static final int DISTRIBUTED_LOCK_TIME = 10;

    /**
     * 默认最大等待时间,此为5000ms
     */
    private static final int DISTRIBUTED_DEFAULT_MAX_WAIT_TIME = 5000;

    /**
     * 默认单次等待时间,此为50ms
     */
    private static final int DISTRIBUTED_DEFAULT_PER_WAIT_TIME = 50;

    /**
     * 分布式锁中对应value的随机位数
     */
    private static final int DISTRIBUTED_KEY_SIGNATURE_LENGTH = 6;


    /**
     * 使用redis来获取分布式锁,返回的为当前设置的锁的签名,设置这个锁的签名是为了避免当前线程A拿到分布式锁后,阻塞时间过长,导致锁被超时删除
     * ,被线程B拿到,线程A执行完成之后,可能又把线程B的删除掉,导致线程C又进入。
     *
     * @param key 对应key
     * @return 但成功时返回的为当前设置的锁的签名, 失败时返回的为""空字符串
     */
    private String lock(String key) {

        // 得到一个随机的字符串
        String keySignature = getRandomNumber();

        long reply = jedis.setnx(key, keySignature);
        if (reply > 0) {
            // 设置过期时间
            jedis.expire(key, DISTRIBUTED_LOCK_TIME);
            return keySignature;
        }
        return "";
    }

    /**
     * 删除分布式锁,注意此会传入keySignature,防止出现上述的误删情况
     *
     * @param key          分布式锁的key
     * @param keySignature 分布式锁的value
     */
    private void unLock(String key, String keySignature) {

        String distributedLockValue = jedis.get(key);
        // 当相等的时候才会删除
        if (keySignature.equals(distributedLockValue)) {
            jedis.del(key);
        }
    }

    @Override
    public boolean tryLock(Runnable runnable, String key) {

        // 先获取分布式锁
        String keySignature = lock(key);
        if (StringUtils.isEmpty(keySignature)) return false;

        // 获取到分布式锁,开始执行任务
        runnable.run();
        // 执行完成后,删除分布式锁
        unLock(key, keySignature);
        return true;
    }

    @Override
    public boolean tryLock(Runnable runnable, String key, int maxWaitTimeMills, int perWaitTimeMills) {

        maxWaitTimeMills = maxWaitTimeMills <= 0 ? DISTRIBUTED_DEFAULT_MAX_WAIT_TIME : maxWaitTimeMills;
        perWaitTimeMills = perWaitTimeMills <= 0 || perWaitTimeMills >= maxWaitTimeMills ? DISTRIBUTED_DEFAULT_PER_WAIT_TIME : perWaitTimeMills;

        long beginTimeMillis = System.currentTimeMillis();
        // 尝试获取分布式锁,并执行任务
        while (!tryLock(runnable, key)) {
            // 当分布式锁失败时

            // 超过最大时间时,不再循环
            if (System.currentTimeMillis() - beginTimeMillis > maxWaitTimeMills) {
                return false;
            }

            // 等待指定时间
            try {
                Thread.sleep(perWaitTimeMills);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    /**
     * 得到分布式锁对应的value,解决阻塞或宕机造成的分布式锁失败的问题
     */
    private String getRandomNumber() {

        StringBuilder randomNumber = new StringBuilder();
        for (int i = 0; i < DISTRIBUTED_KEY_SIGNATURE_LENGTH; i ++) {
            randomNumber.append((int)(Math.random() * 10));
        }
        return randomNumber.toString();
    }

}

  其中 lock 方法即为 setnx 一个分布式锁,如果成功,此方法将会返回锁对应的随机字符串,之后即可使用这个随机字符串进行 unlock 。


三、测试

  写好了分布式锁之后,如果进行测试呢?我们知道,一般在web项目中才会使用分布式锁,所以我们使用web项目来进行测试,而且,每一个请求,都是一个线程来执行,所以我写了如下的测试方法:

import com.happyheng.lock.DistributedLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 *
 * Created by happyheng on 2018/4/6.
 */
@RestController
@RequestMapping("/lock")
public class LockController {

    @Autowired
    private DistributedLockService distributedLockService;

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public String testDistributedLock(HttpServletRequest httpServletRequest) {

        // 测试分布式锁
        distributedLockService.tryLock(new Runnable() {
            @Override
            public void run() {

                String threadName = Thread.currentThread().getName();
                System.out.println("线程" + threadName + "启动");

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程" + threadName + "结束");

            }
        }, "lock_test", 5000, 100);

        return "";
    }



}

  可以看到,每次请求过来的时候,我都会执行一个runnable,里面会阻塞500ms,假如多个请求同时过来的话,同一时间也只有一个runnable会执行。

  意思是如果分布式锁是成功的话,那么结果应该为

  线程A启动
  线程A结束
  线程B启动
  线程B结束
  线程C启动
  线程C结束

  而不是

  线程A启动
  线程B启动
  线程C启动
  线程A结束
  线程B结束

  线程C结束

  经测试,结果为

  

  符合我们的预期。

四、结语:

  我们最终实现了基于Redis的分布式锁,源码地址为 项目源码地址 ,在下一节中,我们将会使用注解来扩展分布式锁,使分布式锁能更好的融入到我们的项目中


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值