redis实现分布式锁

本文通过对比Java synchronized关键字,介绍了如何利用Redis实现分布式锁,并提供了一种实现方式,包括加锁、解锁的过程。

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

    在说redis实现分布式锁之前我们先引入Java的synchronized关键字概念,在并发变成中,线程安全是我们需要关注的重要的一点,之所以会造成线程不安全的原因一般有两点(1)存在共享数据(2)多个线程在操作共享数据.所以当多个线程在操作他们的共享数据时,我们要保证在同一时刻只有一个线程在操作该共享数据,其他的线程需要等该线程处理完毕后再进行操作该数据,这个就叫互斥锁.synchronized关键字可以解决这个问题,我们来看一个例子

public class TestLock implements Runnable{

    static int t = 0;  //t为线程t1,t2的共享数据

    @Override
    public void run() {
        for (int i=0; i<1000000; i++) {
            addT();
        }
    }

    public void addT() {
        t++;
    }

    public static void main(String[] args) throws Exception{
        TestLock testLock = new TestLock();
        Thread t1 = new Thread(testLock);
        Thread t2 = new Thread(testLock);

        t1.start();
        t2.start();

        //等待t1,t2两个线程运行结束时才执行主线程打印t值
        t1.join();
        t2.join();
        System.out.println(t);
    }
} 

执行结果为


并非是预测中的2000000,就是因为两个线程同事操作了他们的共享数据t,使用关键字synchronized后有效避免此类状况发生

public class TestLock implements Runnable{

    static int t = 0;

    @Override
    public void run() {
        for (int i=0; i<1000000; i++) {
            addT();
        }
    }

    public synchronized void addT() {    //保证同一时刻只有一个线程操作t值
        t++;
    }

    public static void main(String[] args) throws Exception{
        TestLock testLock = new TestLock();
        Thread t1 = new Thread(testLock);
        Thread t2 = new Thread(testLock);

        t1.start();
        t2.start();

        //等待t1,t2两个线程运行结束时才执行主线程打印t值
        t1.join();
        t2.join();
        System.out.println(t);
    }
}

运行结果为


但是在高并发的情况下,使用synchronized会有性能瓶颈,具体原因我也还在学习,所以此处我们引入性能更好的redis分布式锁去解决问题这里需要使用到redis的两个方法getSet和setnx,这两个方法具体不多说,可以看文档介绍

http://redis.cn/commands/getset.html

http://redis.cn/commands/setnx.html

ok,首先我们先简单的写一个操作redis的类

package redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisClient {

    private static String address = "127.0.0.1";

    private static int port = 6379;

    private static String auth = null;

    private static int MAX_IDLE = 10;

    private static int MAX_WAIT = 10000;

    private static int TIMEOUT = 10000;

    private static boolean TEST_ON_BORROW = true;

    private static JedisPool jedisPool;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(MAX_IDLE);
        config.setMaxWaitMillis(MAX_WAIT);
        config.setTestOnBorrow(TEST_ON_BORROW);
        jedisPool = new JedisPool(config,address,port,TIMEOUT);
    }

    //获取jedis客户端
    public static Jedis getJedis() {
        Jedis jedis = null;
        if(jedisPool != null) {
            jedis = jedisPool.getResource();
        }
        return jedis;
    }

    //释放资源
    public static void returnResource(Jedis jedis) {
        jedis.close();
    }
} 

接着我们实现redis的getSet以及setnx方法以及加锁,解锁

package redis;

import redis.clients.jedis.Jedis;

public class TestRedis {

    public static void setString(String key, int exp ,String value) {

        Jedis jedis = RedisClient.getJedis();
        jedis.setex(key,exp,value);
        RedisClient.returnResource(jedis);
    }

    public static void delKey(String key) {
        Jedis jedis = RedisClient.getJedis();
        jedis.del(key);
        RedisClient.returnResource(jedis);
    }

    public static String getByKey(String key) {
        Jedis jedis = RedisClient.getJedis();
        String ret = jedis.get(key);
        RedisClient.returnResource(jedis);
        return ret;
    }

    public static String getSetKey(String key, String value) {
        Jedis jedis = RedisClient.getJedis();
        String ret = jedis.getSet(key,value);
        RedisClient.returnResource(jedis);
        return ret;
    }

    public static Long setNx(String key, String value) {
        Jedis jedis = RedisClient.getJedis();
        Long ret = Long.valueOf(jedis.setnx(key,value));
        RedisClient.returnResource(jedis);
        return ret;
    }


    /**
     * 加锁
     * @param key
     * @param val  当前时间+超时时间
     * @return
     */
    public static Boolean lock(String key, String val) {
        if(setNx(key,val) == 1) {
            return true;
        }
        String currentValue = getByKey(key);
        //如果锁过期(只让一个线程拿到锁)
        if(!currentValue.isEmpty() && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //获取上一个锁的时间
            String oldValue = getSetKey(key,val);
            if(!oldValue.isEmpty() && oldValue.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 解锁
     * @param key
     * @param val
     */
    public static void unlock(String key , String val) {
        String currentValue = getByKey(key);
        if(!currentValue.isEmpty() && currentValue.equals(val)) {
            delKey(key);
        }
    }

    public static void main(String[] args) {

    }
}

此时我们的业务代码可以改变成

import redis.TestRedis;

public class TestLock implements Runnable{

    static int t = 10000;
    static int j = 0;

    private static final int TIMEOUT = 10 * 1000;  //超时时间

    @Override
    public void run() {
        for (int i=0; i<10000; i++) {
            addT();
        }
    }

    public void addT() {
        //加锁
        long time = System.currentTimeMillis() + TIMEOUT;
        if(!TestRedis.lock("123",String.valueOf(time))){
            //抛出异常
            throw new RuntimeException("加锁失败");
        }

        //执行业务逻辑
        t--;
        j++;

        //解锁
        TestRedis.unlock("123",String.valueOf(time));
    }

    public static void main(String[] args) throws Exception{
        TestLock testLock = new TestLock();
        Thread t1 = new Thread(testLock);
        Thread t2 = new Thread(testLock);

        t1.start();
        t2.start();

        //等待t1,t2两个线程运行结束时才执行主线程打印t值
        t1.join();
        t2.join();
        System.out.println(t);
        System.out.println(j);
    }
}

运行结果为


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值