一、什么是分布式锁?

在多线程程序中,不予许多个线程同时操作某个变量或者同时执行某一代码块,我们就需要用来实现。在Java中,可以用synchronized或Lock接口的实现类来实现。那么什么是分布式锁呢?当我们的应用通过分布式部署,每个应用部署在不同的机器上,但是我们要保证这些不同机器上的同一方法在同一时间不能被多个线程执行,这时候就要用到分布式锁。

二、实现分布式锁的几种方式

1.基于数据库实现分布式锁

2.基于Redis实现分布式锁

3.基于zoomkeeper

分布式锁有很多种实现方式,这里我们介绍Redis实现方式。

三、实现代码

配置RedisTemplate

package com.example.redislock.Config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisLockConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Bean
    public RedisTemplate redisTemplate(){
        RedisTemplate redisTemplate = new RedisTemplate();
//对key和value序列化
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置连接方式
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

原理:由于Redis中key和value的唯一性,保证每次写入数据的时候只能写入一条,进而根据写入结果判定是否获取锁(key为当前共享资源设定的唯一资源key,value值线程唯一确保是当前线程亲自释放key)

package com.example.redislock.Service.Impl;

import com.example.redislock.Service.RedisLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

@Service
public class RedisLockServiceImpl implements RedisLockService {
    @Autowired
    public RedisTemplate redisTemplate;
    //    解锁原子性操作脚本
    public static final String unlockScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
            + "then\n"
            + "    return redis.call(\"del\",KEYS[1])\n"
            + "else\n"
            + "    return 0\n"
            + "end";

    /**
     * 加锁,有阻塞
     *
     * @param key
     * @param value
     * @param expire
     * @param timeout
     * @return
     */
    @Override
    public Boolean lock(String key, String value, long expire, long timeout) throws UnsupportedEncodingException {
        long startTime = System.currentTimeMillis();
        do {
            if (!tryLock(key, value, expire)) {
                //设置等待时间10秒,若等待时间过长则获取锁失败
                if ((System.currentTimeMillis() - startTime) > (timeout - 1000)) {
                    return false;
                }
                try {
                    Thread.sleep(50);//try it again per 50
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                return true;
            }
        } while (!tryLock(key, value, expire));
        return true;
    }

    /**
     * 解锁
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public Boolean unlock(String key, String value) throws UnsupportedEncodingException {
        byte[][] keyArgs = new byte[2][];
        keyArgs[0] = key.getBytes(Charset.forName("UTF-8"));
        keyArgs[1] = value.getBytes(Charset.forName("UTF-8"));
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            Long result = connection.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keyArgs);
            if (result != null && result > 0) {
                return true;
            }
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
        return false;
    }

    /**
     * 加锁,无阻塞
     *
     * @param name
     * @param expire
     * @return
     */
    @Override
    public Boolean tryLock(String name, String value, long expire) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            return connection.set(name.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")),
                    Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }
}

测试类

package com.example.redislock.Controller;

import com.example.redislock.Service.RedisLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

@RestController
@RequestMapping("/api")
public class RedisLockController {
    private static final String IDENTIFIER = "currentTask";
    @Autowired
    RedisLockService redisLockService;
    @PostMapping("123")
     public void test() throws UnsupportedEncodingException, InterruptedException {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisLockService.lock(IDENTIFIER, uuid,10000, 11000);
        if(lock){
             System.out.println("我拿到锁了哦!"+Thread.currentThread().getName());
             Thread.sleep(2000);
         }
         else{
             System.out.println("我没有拿到锁!"+Thread.currentThread().getName());
         }
         redisLockService.unlock(IDENTIFIER,uuid);
     }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐