假设有这样的场景,我们用多线程给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。
分布式锁实现的核心在于:需要获取一把锁,这个锁只有一把,一个线程获取之后,其他线程进入等待,某一线程获取锁,并且运行完了相关操作,表示锁用完了,这时候需要调用释放锁的方法,让别的线程获得锁,继续运行下一步的操作。