Redis修改数据多线程并发-并发锁
其实说多线程修改数据也不合适,毕竟redis服务端是单线程的,所有命令串行执行,只是在客户端并发发送命令的时候,导致串行的命令一些排列问题和网络时间差等造成数据不一致。
先写一个实际的例子,测试多线程并发修改redis的某个键值,而结果不是我们想要的。
2个线程同时修改键值mykey1,初始值为0,每次加1,线程共循环100次,2个线程下来,结果应该为200,但实际不是,例子代码如下:
TestRedis类的代码如下:
public class TestRedis {
static JedisSingleHandle jedis = new JedisSingleHandle();
public static void main(String[] args)
{
try {
jedis.set("mykey1", 0);
} catch (Exception e) {
e.printStackTrace();
}
TestRedisConcurrency myThread1 = new TestRedisConcurrency("a");
TestRedisConcurrency myThread2 = new TestRedisConcurrency("b");
myThread1.start();
myThread2.start();
System.out.println("等待两个线程结束");
}
}
TestRedisConcurrency 类的代码如下:
public class TestRedisConcurrency extends Thread {
JedisSingleHandle jedis = new JedisSingleHandle();
private String name;
TestRedisConcurrency(String name){
this.name = name;
System.out.println("线程="+this.name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++)
{
try {
int result = (int) jedis.get("mykey1");
jedis.set("mykey1", result + 1);
} catch (Exception e) {
e.printStackTrace();
}
}
//
try {
System.out.println("线程"+name+"结束,输出" + jedis.get("mykey1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
运行TestRedis ,结果如下:
线程b结束,输出105
线程a结束,输出107
是的,和我们单线程,跑两个100,会输出200。现在是两个并发线程同时跑,由于并发,造成的数据结果往往不是我们想要的。那么如何解决这个问题呢?
1,在我的JedisSingleHandle类中加如下2个方法:
/**
* 获取分布式锁
* @param lockName 竞争获取锁key
* @param acquireTimeoutInMS 获取锁这个动作的超时时间
* @param lockTimeoutInMS 锁的超时时间(毫秒数):需要保证大于1秒,所以lockTimeoutInMS要大于1000
* @return 获取锁标识
*/
public String acquireLockWithTimeout(String lockName, long acquireTimeoutInMS, long lockTimeoutInMS) {
Jedis jedis = null;
String retIdentifier = null;
try {
if (jedis == null) {
jedis = JedisUtil.getJedisPool().getResource();
jedis.select(JedisUtil.dbIndex0);
}
String identifier = UUID.randomUUID().toString();
String lockKey = "lock:" + lockName;
int lockExpire = (int) (lockTimeoutInMS / 1000); // 锁的超时时间(秒数),需要保证大于1秒,所以lockTimeoutInMS要大于1000
long end = System.currentTimeMillis() + acquireTimeoutInMS; // 获取锁这个动作的超时时间
while (System.currentTimeMillis() < end) {
if (jedis.setnx(lockKey, identifier) == 1) { // 成功获取锁
jedis.expire(lockKey, lockExpire); // 锁的超时时间(秒数)
retIdentifier = identifier;
}
if (jedis.ttl(lockKey) == -1) {
jedis.expire(lockKey, lockExpire); // 锁的超时时间(秒数)
}
//
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
} // end while loop
} catch (Exception e) {
e.printStackTrace();;
} finally {
if (jedis != null)
jedis.close();
}
//
return retIdentifier;
}
/**
* 释放锁
* @param lockName 竞争获取锁key
* @param identifier 释放锁标识
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis jedis = null;
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
if (jedis == null) {
jedis = JedisUtil.getJedisPool().getResource();
jedis.select(JedisUtil.dbIndex0);
}
while (true) {
jedis.watch(lockKey);
if (identifier.equals(jedis.get(lockKey))) {
Transaction trans = jedis.multi();
trans.del(lockKey);
List<Object> results = trans.exec();
if (results == null) {
continue;
}
retFlag = true;
}
jedis.unwatch();
break;
} // end while loop
} catch (Exception e) {
e.printStackTrace();;
} finally {
if (jedis != null)
jedis.close();
}
return retFlag;
}
2,重写TestRedisConcurrency类如下:
public class TestRedisConcurrency extends Thread {
private static Logger logger = LoggerFactory.getLogger(TestRedisConcurrency.class);
JedisSingleHandle jedis = new JedisSingleHandle();
private String name;
private String lockKey;
TestRedisConcurrency(String name, String lockKey){
this.name = name;
this.lockKey = lockKey;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
boolean ok = true;
while (ok) {
String str = jedis.acquireLockWithTimeout(lockKey, 10, 1000); // 锁超时时间需要保证大于1秒,所以lockTimeoutInMS参数要大于1000
if (str != null) {
int result = (int) jedis.get("mykey1");
jedis.set("mykey1", result + 1);
jedis.releaseLock(lockKey, str); // 释放锁
ok = false; // 更新成功后,退出while循环。
}
} // end while loop
} catch (Exception e) {
e.printStackTrace();
}
} // end for loop
//
try {
System.out.println("线程"+name+"结束,输出" + jedis.get("mykey1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
3,重写 TestRedis 类,代码如下:
public class TestRedis {
static JedisSingleHandle jedis = new JedisSingleHandle();
public static void main(String[] args)
{
try {
jedis.set("mykey1", 0);
} catch (Exception e) {
e.printStackTrace();
}
//
String lockKey = "mykey1_lock"; // 多个线程共用的一个锁
TestRedisConcurrency myThread1 = new TestRedisConcurrency("a", lockKey);
TestRedisConcurrency myThread2 = new TestRedisConcurrency("b", lockKey);
myThread1.start();
myThread2.start();
System.out.println("------ 等待两个线程结束");
}
}
4,测试,运行TestRedis,得到的结果如下:
------ 等待两个线程结束
线程b结束,输出190
线程a结束,输出200
测试多次,后面这个线程的结果总是200,测试OK。
浙公网安备 33010602011771号