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张!

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值