在说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);
}
}
运行结果为