1. 锁的获取与释放消耗低;
2. 锁的存在判断与获取操作保持原子性;
3. 程序崩溃时锁的释放;
下面是官方的bench-mark数据:
测试完成了50个并发执行100000个请求。
设置和获取的值是一个256字节字符串。
Linux box是运行Linux 2.6,这是X3320 Xeon 2.5 ghz。
文本执行使用loopback接口(127.0.0.1)。
结果:读的速度是110000次/s,写的速度是81000次/s 。
redis在单次读写的效率和系统消耗上面的数据值得肯定。
redis单个操作都是原子的,但是通过exists和get两种中操作就不一定能保证了,目前有两种方式:
watch
WATCH key [key ...]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
可用版本:
>= 2.2.0
时间复杂度:
O(1)。
返回值:
总是返回 OK 。
setnx
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
从代码编写成本上看,setnx更为方便,watch需要更多的代码完成原子操作。
通过redis的expire设置key的时间,从而避免因为程序崩溃导致的无法获取锁的问题。
EXPIRE key seconds
为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
锁的接口声明及redis实现
package com.stg.lock; /** * @author stg * 分布式锁接口 */ public interface DistributeLock { public boolean lock(); public boolean unlock(); }
package com.stg.lock.redis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import com.stg.lock.DistributeLock; import com.stg.utils.RedisUtils; public class RedisDistributeLock implements DistributeLock{ private static final Logger logger = LoggerFactory.getLogger(RedisDistributeLock.class); /** * 锁的key标识,根据业务逻辑调整 */ private final String key; /** * DistributeLock非单例,Jedis切勿声明为static */ private Jedis jedis; /** * 超时时间,无限等待锁会造成系统崩溃 */ private long timeout = 2 * 1000L; public RedisDistributeLock(String key){ this.key = key; jedis = RedisUtils.getJedis(); } @Override public boolean lock() { long tryLocktime = System.currentTimeMillis(); while(System.currentTimeMillis() - tryLocktime < timeout) { // value可随意设置,存储时间类信息便于问题发生后的时间定位 String value = String.valueOf(System.currentTimeMillis()); // 尝试写入key(上锁) long result = jedis.setnx(key, value); if(result == 1) { // 获取成功,设置有效时间,单是秒 jedis.expire(key, 30); logger.info("{} begin", Thread.currentThread().getName()); return true; } // 等待一段时间后尝试重获取key try { Thread.sleep(50); } catch (InterruptedException e) { logger.error(e.getMessage(), e); } } return false; } @Override public boolean unlock() { long result = jedis.del(key); if(result != 1){ logger.warn("unlock fail ,key -> " + key); } logger.info("{} end", Thread.currentThread().getName()); return result == 1; } }
工厂类
package com.stg.lock; /** * @author stg * @param <T> 标识锁的key * * 工厂类,用于创建锁对象 */ public interface DistributeLockFactory<T> { public DistributeLock getLockBean(T key); }
package com.stg.lock.redis; import com.stg.lock.DistributeLock; import com.stg.lock.DistributeLockFactory; public class RedisDistributeLockFactory implements DistributeLockFactory<String> { @Override public synchronized DistributeLock getLockBean(String key) { DistributeLock lockBean = new RedisDistributeLock(key); return lockBean; } }
其他工具类
package com.stg.utils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisUtils { private static JedisPool pool; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxActive(1000); config.setMaxIdle(100); config.setMaxWait(100L); config.setTestOnBorrow(true); try{ pool = new JedisPool(config, "127.0.0.1", 6379, 3000); } catch(Exception e) { e.printStackTrace(); } } public static Jedis getJedis(){ Jedis jedis = pool.getResource(); return jedis; } }
测试类
package com.stg; import java.io.Serializable; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.stg.lock.DistributeLock; import com.stg.lock.DistributeLockFactory; import com.stg.lock.redis.RedisDistributeLockFactory; public class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); private static DistributeLockFactory<String> factory; public static void main(String[] args) { lockOperation(); while(true) { try { Thread.sleep(30*1000L); } catch (InterruptedException e) { logger.error(e.getMessage(), e); } } } public static void lockOperation() { factory = new RedisDistributeLockFactory(); int threadNum = 10; Executor executor = Executors.newFixedThreadPool(threadNum); for(int i = 0; i< threadNum; i++) { executor.execute(new Runnable(){ @Override public void run() { User user = new User(10001, "zhangsan", 25); DistributeLock lockBean = factory.getLockBean(String.valueOf(user.getId())); try { if (lockBean.lock()) { // 业务逻辑处理 try { /* * ***注意*** * thread * sleeptime < 获取锁的超市时间 * 也就是说业务逻辑决定了锁超时时间的设定 */ Thread.sleep(100L); } catch (InterruptedException e) { logger.error(e.getMessage(), e); } } else { } } finally { lockBean.unlock(); } } }); } } } class User implements Serializable { private static final long serialVersionUID = -5937959261213855736L; public User() { } public User(long id, String name, int age) { this.id = id; this.name = name; this.age = age; } private long id; private String name; private int age; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
输出结果:
pool-1-thread-4 begin
pool-1-thread-4 end
pool-1-thread-5 begin
pool-1-thread-5 end
pool-1-thread-1 begin
pool-1-thread-1 end
pool-1-thread-6 begin
pool-1-thread-6 end
pool-1-thread-7 begin
pool-1-thread-7 end
pool-1-thread-2 begin
pool-1-thread-2 end
pool-1-thread-3 begin
pool-1-thread-3 end
pool-1-thread-10 begin
pool-1-thread-10 end
pool-1-thread-9 begin
pool-1-thread-9 end
pool-1-thread-8 begin
pool-1-thread-8 end