就用模拟抢票来实现锁,先看看不加任何锁的情况。
以下是测试代码。
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张!