java利用redis实现分布式锁

假设有这样的场景,我们用多线程给redis里面一个变量,比如money=1000做减法,每次将变量-100,这样,10次操作之后,我们期望的是0,在实际中,我们通过编码来看看这个结果:

RedisUtil.java 用jedis线程池来操作redis

package com.xxx.redis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtil {
	public static final JedisPool pool;
	static{
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxIdle(200);
		config.setMaxWaitMillis(1000 * 100);
		config.setTestOnBorrow(false);
		pool = new JedisPool(config, "127.0.0.1", 6379);
	}
}

DistributedLock.java

package com.xxx.redis;
import java.util.concurrent.TimeUnit;
import redis.clients.jedis.Jedis;
public class DistributedLock {
	
	public static Runnable notLock() throws InterruptedException{
		return ()->{
			try (Jedis jedis = RedisUtil.pool.getResource()) {
				calImpl(jedis);
			} catch (Exception e) {
				e.printStackTrace();
			}
		};
	}

	public static void calImpl(Jedis jedis) {
		int money = Integer.parseInt(jedis.get("money"));
		jedis.set("money", String.valueOf(money-100));
		System.out.println("calc -> " + Thread.currentThread().getName() );
	}
	
	public static void main(String[] args) throws Exception{
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			jedis.set("money", "1000");
		} catch (Exception e) {
			e.printStackTrace();
		}
		Runnable runnable = notLock();
		for(int i=0;i<10;i++){
			Thread thread  = new Thread(runnable,String.valueOf(i));
			thread.start();
		}
		TimeUnit.SECONDS.sleep(5);
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			int money = Integer.parseInt(jedis.get("money"));
			System.out.println("money -> "+money);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

普通的代码,不使用锁来操作,结果不是我们想象中的那样:

 

这个结果,出乎了我们的意料,多线程环境下,最终的money并不是0,而是400,而且,每次运行结果可能都不一样。这就是多线程环境下,造成的变量问题。 解释的话,就是当一个线程获取到了redis中money的值比如900,还没有进行赋值-100操作的时候,另一个线程也获取了redis中money的值900,这样,他也来操作,在同一时间里,两个线程同时做了-100操作,但是结果却是一样的,最终经过10次这样的-100操作,我们的money就可能变为了我们运行打印的结果了。

既然问题出在了获取money的值并做减法操作这个过程中,那么,问题就可以有解决办法了,分布式锁,这就是本文要介绍的:

 分布式锁的实现方式有很多,这里使用redis来实现分布式锁:

自定义Lock.java接口:

package com.xxx.redis;
import redis.clients.jedis.Jedis;
public interface Lock {
	public boolean tryLock(Jedis jedis,String key,String val,Long ttl);
	public boolean releaseLock(Jedis jedis,String key,String val);
}

实现Lock.java接口 LockInitial.java:

package com.xxx.redis;
import java.util.Random;
import redis.clients.jedis.Jedis;
public class LockInitial implements Lock{
	public static Random random = new Random();
	
	@Override
	public boolean tryLock(Jedis jedis,String key,String val,Long ttl){
		String result = jedis.set(key, val,"NX","EX",ttl);
		return "OK".equals(result);
	}
	
	@Override
	public boolean releaseLock(Jedis jedis,String key,String val){
		if(val.equals(jedis.get(key))){
			return jedis.del(key) > 0;
		}
		return false;
	}
}

完整的DistributedLock.java:

package com.xxx.redis;

import java.util.concurrent.TimeUnit;

import redis.clients.jedis.Jedis;

public class DistributedLock {
	
	public static Runnable notLock() throws InterruptedException{
		return ()->{
			try (Jedis jedis = RedisUtil.pool.getResource()) {
				calImpl(jedis);
			} catch (Exception e) {
				e.printStackTrace();
			}
		};
	}
	
	public static Runnable lock() throws InterruptedException{
		LockInitial lockInitial = new LockInitial();
		return ()->{
			try (Jedis jedis = RedisUtil.pool.getResource()) {
				calc(lockInitial,jedis);
			} catch (Exception e) {
				e.printStackTrace();
			}
		};
	}
	
	
	public static void calc(LockInitial lockInitial,Jedis jedis){
		if(lockInitial.tryLock(jedis, "money_lock", "100", 1000l)){
			calImpl(jedis);
			lockInitial.releaseLock(jedis, "money_lock", "100");
		}else{
			calc(lockInitial, jedis);
		}
	}
	
	public static void calImpl(Jedis jedis) {
		int money = Integer.parseInt(jedis.get("money"));
		jedis.set("money", String.valueOf(money-100));
		System.out.println("calc -> " + Thread.currentThread().getName() );
	}
	
	public static void main(String[] args) throws Exception{
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			jedis.set("money", "1000");
		} catch (Exception e) {
			e.printStackTrace();
		}
		Runnable runnable = lock();
		for(int i=0;i<10;i++){
			Thread thread  = new Thread(runnable,String.valueOf(i));
			thread.start();
		}
		TimeUnit.SECONDS.sleep(5);
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			int money = Integer.parseInt(jedis.get("money"));
			System.out.println("money -> "+money);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

使用分布式锁之后,运行打印结果:

 

最终结果符合我们的预期,1000经过10次-100操作,最终变为0。

分布式锁实现的核心在于:需要获取一把锁,这个锁只有一把,一个线程获取之后,其他线程进入等待,某一线程获取锁,并且运行完了相关操作,表示锁用完了,这时候需要调用释放锁的方法,让别的线程获得锁,继续运行下一步的操作。 

### 使用 JavaRedis 实现分布式锁 #### 1. 前置知识 Redis 是一种高性能的键值数据库,支持丰富的数据结构操作。Jedis 或 Lettuce 是常用的 Redis 客户端库,在 Java 中可以通过这些库与 Redis 进行交互。 为了实现分布式锁,通常会利用 Redis 的原子性命令 `SETNX`(Set if Not Exists),它可以在键不存在时设置键,并返回成功标志。此外,为了避免死锁问题,需要为锁设置一个合理的过期时间[^1]。 --- #### 2. 解决方案设计 ##### 2.1 获取锁 通过 `SETNX` 命令尝试创建一个唯一的键值对作为锁。如果键已经存在,则表示锁已被占用;否则,成功获取锁并为其设置一个短暂的过期时间以防止死锁[^2]。 ##### 2.2 释放锁 在释放锁之前,需验证当前线程是否是该锁的实际拥有者。这一步骤可通过 Lua 脚本来完成,确保整个过程具备原子性,从而避免误删其他客户端持有的锁[^3]。 --- #### 3. 示例代码 以下是基于 Jedis 库的一个简单分布式锁实现: ```java import redis.clients.jedis.Jedis; import java.util.UUID; public class DistributedLock { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private Jedis jedis; // Redis 客户端实例 private String lockKey; // 锁对应的 key private String requestId; // 请求 ID (唯一标识符) public DistributedLock(Jedis jedis, String lockKey) { this.jedis = jedis; this.lockKey = lockKey; this.requestId = UUID.randomUUID().toString(); } /** * 尝试加锁 * * @param expireTime 锁的有效时间(毫秒) * @return 是否成功获得锁 */ public boolean tryLock(long expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); return LOCK_SUCCESS.equals(result); // 如果返回 OK 则表示成功获取到锁 } /** * 释放锁 * * 使用 Lua 脚本保证解锁操作的原子性 */ public void releaseLock() { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; jedis.eval(script, java.util.Collections.singletonList(lockKey), java.util.Collections.singletonList(requestId)); } } ``` --- #### 4. 主程序调用示例 以下是一个完整的主函数演示如何使用上述类来管理分布式锁: ```java import redis.clients.jedis.Jedis; public class MainApp { public static void main(String[] args) throws InterruptedException { Jedis jedis = new Jedis("localhost", 6379); DistributedLock distributedLock = new DistributedLock(jedis, "my_lock"); if (distributedLock.tryLock(10000L)) { // 尝试获取锁,有效时间为 10 秒 System.out.println(Thread.currentThread().getName() + ": 成功获取锁"); try { // 执行业务逻辑... Thread.sleep(5000); // 模拟耗时任务 } finally { distributedLock.releaseLock(); // 确保最终释放锁 System.out.println(Thread.currentThread().getName() + ": 已释放锁"); } } else { System.out.println(Thread.currentThread().getName() + ": 获取锁失败"); } jedis.close(); } } ``` --- #### 5. 方案优缺点分析 - **优点** - 支持高并发环境下的资源竞争控制。 - 提供了超时机制,能够自动解除未被正常释放的锁,减少死锁风险[^1]。 - **缺点** - 单一节点 Redis 存在单点故障隐患。可考虑引入 Redis Sentinel 或 Cluster 来提升可靠性[^2]。 - 若网络分区发生,可能导致部分客户端无法及时更新状态而引发一致性问题[^3]。 --- ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luffy5459

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值