Java实现Redis分布式锁

就用模拟抢票来实现锁,先看看不加任何锁的情况。

以下是测试代码。

public class App {
	private static int count = 100;

	public static void main(String[] args) throws InterruptedException {
		TicketThread ticketThread = new TicketThread();
		Thread t1 = new Thread(ticketThread, "客户端1");
		Thread t2 = new Thread(ticketThread, "客户端2");
		Thread t3 = new Thread(ticketThread, "客户端3");
		Thread t4 = new Thread(ticketThread, "客户端4");
		Thread t5 = new Thread(ticketThread, "客户端5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

	static class TicketThread implements Runnable {
		@Override
		public void run() {
			while (count > 0) {
				System.out.println("线程:" + Thread.currentThread().getName() + ",抢到了第:" + (count--) + "张!");
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

部分返回结果,可以发现有重复卖的情况。 

线程:客户端2,抢到了第:3张!
线程:客户端5,抢到了第:2张!
线程:客户端3,抢到了第:2张!
线程:客户端1,抢到了第:2张!
线程:客户端4,抢到了第:2张!
线程:客户端2,抢到了第:1张!

我们试试加Java自带的锁。

//加个java自带的锁
private static Lock lock = new ReentrantLock();


lock.lock();
try {
    System.out.println("线程:" + Thread.currentThread().getName() + ",抢到了第:" + (count--) + "张!");
} finally {
    lock.unlock();
}

可以看到已经解决了超卖情况,如果是分布式这种锁就没办法解决了 。

线程:客户端1,抢到了第:10张!
线程:客户端5,抢到了第:9张!
线程:客户端2,抢到了第:8张!
线程:客户端3,抢到了第:7张!
线程:客户端4,抢到了第:6张!
线程:客户端1,抢到了第:5张!
线程:客户端2,抢到了第:4张!
线程:客户端5,抢到了第:3张!
线程:客户端3,抢到了第:2张!
线程:客户端4,抢到了第:1张!

我们看看Redis实现分布式锁,首先导入pom文件

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.1</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
	<version>2.0.4.RELEASE</version>
</dependency>

RedisLock.java

import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.Jedis;

import java.time.Duration;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class RedisLock implements Lock {
	//存到redis里面的
	private static final String LOCK_KEY = "lockKey";
	private JedisConnectionFactory factory;
	private ThreadLocal<String> local = new ThreadLocal<>();

	public RedisLock() {
		//初始化连接工厂
		RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
		redisStandaloneConfiguration.setHostName("localhost");
		redisStandaloneConfiguration.setPort(6379);
		redisStandaloneConfiguration.setDatabase(0);
		JedisClientConfiguration.JedisClientConfigurationBuilder configurationBuilder = JedisClientConfiguration.builder();
		configurationBuilder.connectTimeout(Duration.ofMillis(3000));
		factory = new JedisConnectionFactory(redisStandaloneConfiguration, configurationBuilder.build());
	}


	@Override
	public void lock() {
		//一直尝试去拿锁
		while (!tryLock()) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {

	}

	@Override
	public boolean tryLock() {
		/*
		 * 生成uuid防止解锁的时候解错了,为什么会出现解错的情况呢?
		 * 因为这边设置的是5秒自动失效,如果程序运行超过了6秒,此时
		 * 锁已经可能被别人拿到了,你再去删就会删错,所以这里弄个uuid
		 * 删之前先判断一下,如果是自己设置的再删除!
		 * 不用担心死锁问题,因为5000(5秒)后会自动失效。实际情况
		 * 根据自己的业务需求来设置,假如你的程序3秒可以跑完,就设置6秒
		 * 也就是 业务时间*2
		 */
		String uuid = UUID.randomUUID().toString();
		String ret;
		try (Jedis jedis = (Jedis) factory.getConnection().getNativeConnection()) {
			ret = jedis.set(LOCK_KEY, uuid, "NX", "PX", 5000);
		}
		if ("OK".equals(ret)) {
			// 因为多线程情况,所以放到thread local里
			local.set(uuid);
			return true;
		}
		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return false;
	}

	@Override
	public void unlock() {
		try (Jedis jedis = (Jedis) factory.getConnection().getNativeConnection()) {
			/*
			 * if redis.call("get",KEYS[1]) == ARGV[1] then
			 * 		return redis.call("del",KEYS[1])
			 * else
			 * 		return 0
			 * end
			 */
			//这里不用管返回结果,成功就成功失败了也没关系。
			//这里使用lua脚本是保证原子性,因为redis是单线程不用担心并发问题。
			jedis.eval("if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end", Collections.singletonList(LOCK_KEY), Collections.singletonList(local.get()));
			local.remove();
		}
	}

	@Override
	public Condition newCondition() {
		return null;
	}
}

把 App.java 锁改成 Redis 的 

private static Lock lock = new RedisLock();

我们看下部分返回结果

线程:客户端5,抢到了第:12张!
线程:客户端3,抢到了第:11张!
线程:客户端2,抢到了第:10张!
线程:客户端1,抢到了第:9张!
线程:客户端4,抢到了第:8张!
线程:客户端5,抢到了第:7张!
线程:客户端3,抢到了第:6张!
线程:客户端2,抢到了第:5张!
线程:客户端1,抢到了第:4张!
线程:客户端4,抢到了第:3张!
线程:客户端5,抢到了第:2张!
线程:客户端3,抢到了第:1张!

 

Java实现Redis分布式锁可以通过使用Redis的SETNX命令来实现。SETNX命令可以在Redis中设置一个键值对,但只有在键不存在的情况下才会设置成功。因此,可以将某个键作为锁的标识,当某个线程成功执行SETNX命令并获得锁时,其他线程执行SETNX命令时会失败,从而实现分布式锁的效果。在释放锁时,可以使用Redis的DEL命令来删除对应的键。 另外,Java中也可以使用显式锁(Lock)来实现分布式锁。通过使用ReentrantLock类,可以在代码中显式地加锁和解锁。在同一个线程中,当外层方法获取锁后,再进入内层方法时会自动获取锁,不会因为之前已经获取过锁而阻塞。这种可重入锁的特性可以一定程度上避免死锁的发生。 总结起来,Java实现Redis分布式锁可以通过使用Redis的SETNX命令或者使用显式锁(Lock)来实现。具体的实现方式可以根据实际需求和场景选择。\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Java --- redis7实现分布式锁](https://blog.youkuaiyun.com/qq_46093575/article/details/130661856)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Redis分布式锁的正确实现方式(Java版)](https://blog.youkuaiyun.com/zth_killer/article/details/106853052)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值