错误方法:
setnx获取锁,拿到锁用expire给锁加一个过期时间,防止锁忘记释放。如果setnx执行之后expire执行之前,线程死掉,那锁就永远得不到释放,发生死锁。
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 线程死掉,无法设置过期时间,发生死锁
jedis.expire(lockKey, expireTime);
}
最佳实践:set=setnx和expire,一条指令,把setnx和expire原子化结合起来。
set key value [ex seconds] [px milliseconds] [nx|xx]
ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在, 才可以设置成功, 用于添加。
xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。
redis连接获取工具类
package com.hong.api.redis.lock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 单机模式
*/
public class RedisUtil {
private static final String redisHost = "192.168.1.200";
private static final int port = 6379;
private static JedisPoolConfig config = null;
private static JedisPool pool = null;
static {
// 利用Redis连接池,保证多个线程利用多个连接,充分模拟并发性
config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxTotal(30);
config.setMaxWaitMillis(100000);
// config.setTestOnBorrow(true);
// config.setTestOnReturn(true);
pool = new JedisPool(config, redisHost, port);
}
public static Jedis getJedis() {
return pool.getResource();
}
public static int getNumActive() {
return pool.getNumActive();
}
public static void returnResource(Jedis jedis) {
// pool.returnBrokenResource(jedis);
pool.returnResource(jedis);
}
}
获取分布式锁
package com.hong.api.redis.lock;
import java.util.Random;
import java.util.UUID;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class RedisLock {
/**
* 获取锁:生成一个UUID,作为Key的标识,不断轮询lockName,直到set成功,表示成功获取锁。
*/
public static TmpResult getLock(String lockNameKey) {
String value = UUID.randomUUID().toString();
Jedis jedis = null;
for (; ; ) {
try {
jedis = RedisUtil.getJedis();
if ("OK".equals(jedis.set(lockNameKey, value, "NX", "EX", 10))) {
System.out.println("get lock");
RedisUtil.returnResource(jedis);
TmpResult tmpResult = new TmpResult();
tmpResult.setJedis(jedis);
tmpResult.setValue(value);
return tmpResult;
}
// 阻塞之前,先放入redis pool,避免消费者线程太多把redis pool全部占满
RedisUtil.returnResource(jedis);
// 时间短,竞争比较激烈,性能差
// Thread.currentThread().sleep(100);
Thread.currentThread().sleep(new Random().nextInt(5000));
// System.out.println("get lock fail");
} catch (Throwable t) {
t.printStackTrace();
if (jedis != null) {
RedisUtil.returnResource(jedis);
}
}
}
}
/**
* 释放锁:对lockName做watch,开启一个事务,删除以LockName为key的锁,删除后此锁对于其他线程为可争抢。
*/
public static void relaseLock(String lockNameKey, TmpResult tmpResult) {
Jedis jedis = null;
try {
jedis = tmpResult.getJedis();
while (true) {
jedis.watch(lockNameKey);
if (jedis.get(lockNameKey).equals(tmpResult.getValue())) {
Transaction tx = jedis.multi();
tx.del(lockNameKey);
tx.exec();
return;
}
jedis.unwatch();
}
} finally {
if (jedis != null) {
RedisUtil.returnResource(jedis);
}
}
}
}
package com.hong.api.redis.lock;
import redis.clients.jedis.Jedis;
public class TmpResult {
private Jedis jedis;
private String value;
public Jedis getJedis() {
return jedis;
}
public void setJedis(Jedis jedis) {
this.jedis = jedis;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
测试
package com.hong.api.redis.lock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class RedisLockTest {
private static String lockNameKey = "lock_key";
private static int count = 0;
private static AtomicInteger Countor = new AtomicInteger(0);
private static int ThLeng = 1024;
// CountDownLatch保证主线程在全部线程结束之后退出
private static CountDownLatch latch = new CountDownLatch(ThLeng);
private static ExecutorService service = Executors.newFixedThreadPool(ThLeng);
public static void main(String args[]) {
for (int i = 0; i < ThLeng; ++i) {
String threadName = "thread-" + i;
Thread t = new Thread(new SubAddThread(threadName));
System.out.println(threadName + " init...");
service.submit(t);
}
service.shutdown();
try {
latch.await();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println(Countor.get());
System.out.println(count);
}
public static class SubAddThread implements Runnable {
private String threadName;
public SubAddThread(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
System.out.println(threadName + " starting...");
for (int i = 0; i < 50; ++i) {
TmpResult tmpResult = RedisLock.getLock(lockNameKey);
// System.out.println(threadName + " get Lock");
count++;
Countor.incrementAndGet();
RedisLock.relaseLock(lockNameKey, tmpResult);
System.out.println(threadName + " " + count);
}
latch.countDown();
System.out.println(threadName + " over");
}
}
}