一、Redis实现
1.1 基本用法
- Redis.java
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class Redis {
private JedisPool jedisPool;
public Redis() {
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<Jedis>();
poolConfig.setMaxTotal(20);
poolConfig.setMaxIdle(10);
poolConfig.setMinIdle(5);
jedisPool = new JedisPool(poolConfig, "114.116.XX.XXX", 6379, 50000, "123");
}
public void execute(IJedisExec iJedisExec) {
Jedis jedis = jedisPool.getResource();
iJedisExec.call(jedis);
jedisPool.returnResource(jedis);
}
}
- IJedisExec.java
import redis.clients.jedis.Jedis;
public interface IJedisExec {
public void call(Jedis jedis);
}
- LockTest .java
public class LockTest {
public static void main(String[] args) {
Redis redis = new Redis();
redis.execute(jedis -> {
long setnx = jedis.setnx("k1", "v1");
if (setnx == 1) {
//设置过期时间,确保锁在一定的时间之后,能够得到释放。
jedis.expire("k1", 5);
jedis.set("name", "ahao");
System.out.println(jedis.get("name"));
jedis.del("k1");//释放资源
}else {
//有人操作,停止/暂缓 操作
}
});
}
}
分布式锁实现的思路很简单,就是进来一个线城先占位,当别的线城进来操作时,发现已经有人占位
了,就会放弃或者稍后再试。
在 Redis 中,占位一般使用 setnx 指令,先进来的线城先占位,线城的操作执行完成后,再调用 del 指
令释放位子。
还有一个问题,就是在获取锁和设置过期时间之间如果如果服务器突然挂掉了,这个时
候锁被占用,无法及时得到释放,也会造成死锁,因为获取锁和设置过期时间是两个操作,不具备原子
性。
为了解决这个问题,从 Redis2.8 开始,setnx 和 expire 可以通过一个命令一起来执行了,我们对上述
代码再做改进:
public class LockTest {
public static void main(String[] args) {
Redis redis = new Redis();
redis.execute(jedis -> {
String set = jedis.set("k1", "v1", new SetParams().nx().ex(5));
if (set != null && "OK".equals(set)) {
//给锁添加一个过期时间,防止应用在运行过程中抛出异常导致锁无法及时得到释放
jedis.expire("k1", 5);
//没人占位
jedis.set("name", "ahao");
String name = jedis.get("name");
System.out.println(name);
//释放资源
jedis.del("k1");
} else {
//有人占位,停止/暂缓 操作
}
});
}
}
二、Redisson实现分布式锁
相对于 Jedis 这种原生态的应用,Redisson 对 Redis 请求做了较多的封装,对于锁,也提供了对应的方
法可以直接使用:
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedissionTest {
public static void main(String[] args) {
Config config = new Config();
//配置 Redis 基本连接信息
config.useSingleServer().setAddress("redis://114.116.16.212:6379").setPassword("123");
//获取一个 RedissonClient 对象
RedissonClient client = Redisson.create(config);
//获取一个锁对象实例
RLock lock = client.getLock("lock");
try {
//获取锁
boolean b = lock.tryLock(500, 1000, TimeUnit.MILLISECONDS);
if (b) {
RBucket<Object> bucket = client.getBucket("ahao");
bucket.set("6666666");
Object o = bucket.get();
System.out.println("o = " + o);
} else {
System.out.println("没拿到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
在这段代码中,核心的就是 lock.tryLock(500, 1000, TimeUnit.MILLISECONDS); ,第一个参数是
尝试加锁的等待时间为 500 毫秒,第二个参数表示锁的超时时间为 1000 毫秒,也就是这个锁在 1000
毫秒后会自动失效。