一、前言
对于前面的编写redis锁非常简单,也编写了测试用例,但是用起来是不是感觉毕竟麻烦。今天我们把redis锁,进一步小封装一下,用起来就只有几行代码了。对于没有看文章二的同学可能是个遗憾。
https://github.com/yangchangyong0/redis-jedis-2.4.1_lock 项目下载地址
二、redis分布式锁编写
2.1 编写redis锁
与之前我们编写的redis锁一样,没有变动。我这里全部复制希望你们更方便。
package com.github.jedis.lock; import redis.clients.jedis.Jedis; /** * Redis distributed lock implementation. */ public class JedisLock { Jedis jedis; /** * Lock key path. */ String lockKey; /** * Lock expiration in miliseconds. * 锁超时,防止线程在入锁以后,无限的执行等待 */ int expireMsecs = 60 * 1000; /** * Acquire timeout in miliseconds. * 锁等待,防止线程饥饿 */ int timeoutMsecs = 10 * 1000; boolean locked = false; /** * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs. * * @param jedis * @param lockKey lock key (ex. account:1, ...) */ public JedisLock(Jedis jedis, String lockKey) { this.jedis = jedis; this.lockKey = lockKey; } /** * Detailed constructor with default lock expiration of 60000 msecs. * * @param jedis * @param lockKey lock key (ex. account:1, ...) * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs) */ public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs) { this(jedis, lockKey); this.timeoutMsecs = timeoutMsecs; } /** * Detailed constructor. * * @param jedis * @param lockKey lock key (ex. account:1, ...) * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs) * @param expireMsecs lock expiration in miliseconds (default: 60000 msecs) */ public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs, int expireMsecs) { this(jedis, lockKey, timeoutMsecs); this.expireMsecs = expireMsecs; } /** * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs. * * @param lockKey lock key (ex. account:1, ...) */ public JedisLock(String lockKey) { this(null, lockKey); } /** * Detailed constructor with default lock expiration of 60000 msecs. * * @param lockKey lock key (ex. account:1, ...) * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs) */ public JedisLock(String lockKey, int timeoutMsecs) { this(null, lockKey, timeoutMsecs); } /** * Detailed constructor. * * @param lockKey lock key (ex. account:1, ...) * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs) * @param expireMsecs lock expiration in miliseconds (default: 60000 msecs) */ public JedisLock(String lockKey, int timeoutMsecs, int expireMsecs) { this(null, lockKey, timeoutMsecs, expireMsecs); } /** * @return lock key */ public String getLockKey() { return lockKey; } /** * Acquire lock. * * @return true if lock is acquired, false acquire timeouted * @throws InterruptedException in case of thread interruption */ public synchronized boolean acquire() throws InterruptedException { return acquire(jedis); } /** * Acquire lock. * * @param jedis * @return true if lock is acquired, false acquire timeouted * @throws InterruptedException in case of thread interruption */ public synchronized boolean acquire(Jedis jedis) throws InterruptedException { //锁等待,防止线程饥饿 int timeout = timeoutMsecs; while (timeout >= 0) { //锁超时,防止线程在入锁以后,无限的执行等待(为什么加1:因为可能有相同时间的操作,这样做不完美,但是实用) long expires = System.currentTimeMillis() + expireMsecs + 1; String expiresStr = String.valueOf(expires); //锁到期时间 if (jedis.setnx(lockKey, expiresStr) == 1) { // lock acquired locked = true; return true; } String currentValueStr = jedis.get(lockKey); //redis里的时间 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的 // lock is expired String oldValueStr = jedis.getSet(lockKey, expiresStr); //获取上一个锁到期时间,并设置现在的锁到期时间, //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的 if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { //如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁 // lock acquired locked = true; return true; } } timeout -= 100; Thread.sleep(100); } return false; } /** * Acqurired lock release. */ public synchronized void release() { release(jedis); } /** * Acqurired lock release. */ public synchronized void release(Jedis jedis) { if (locked) { jedis.del(lockKey); locked = false; } } }
2.封装redis锁
对一些参数进行简单的封装,原来我们使用的线程池,现在我们使用线程来实现。
- 实现参数的封装,一般都这么玩耍
- 实现修改为线程实现
<pre name="code" class="java">/** * Copyright (c) 2011-2020 Panguso, Inc. * All rights reserved. * * This software is the confidential and proprietary information of Panguso, * Inc. ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into with Panguso. */ package com.chinaso.phl.concurrent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import com.github.jedis.lock.JedisLock; /** * 分布式锁的简单用法 * @author come from network * @date 2014-3-7 */ public class SimpleLock { private static Logger logger = LoggerFactory.getLogger(SimpleLock.class); private static JedisPool pool; private JedisLock jedisLock; private String lockKey; private Jedis jedis; private int timeoutMsecs; private int expireMsecs; public SimpleLock(String lockKey) { this(lockKey, 3000, 300000); } public SimpleLock(String lockKey, int timeoutMsecs, int expireMsecs) { this.lockKey = lockKey; this.jedis = pool.getResource(); this.timeoutMsecs = timeoutMsecs; this.expireMsecs = expireMsecs; this.jedisLock = new JedisLock(jedis, lockKey.intern(), timeoutMsecs, expireMsecs); } public void wrap(Runnable runnable) { long begin = System.currentTimeMillis(); try { // timeout超时,等待入锁的时间,设置为3秒;expiration过期,锁存在的时间设置为5分钟 logger.info("begin logck,lockKey={},timeoutMsecs={},expireMsecs={}", lockKey, timeoutMsecs, expireMsecs); if (jedisLock.acquire()) { // 启用锁 runnable.run(); } else { //logger.info("The time wait for lock more than [{}] ms ", timeoutMsecs); } } catch (Throwable t) { // 分布式锁异常 logger.warn(t.getMessage(), t); } finally { this.lockRelease(jedisLock, jedis); } logger.info("[{}]cost={}", lockKey, System.currentTimeMillis() - begin); } /** * 释放锁,后期欲将离线计算的释放锁封装 * * @param lock * @param jedis * @author come from network * @date 2014-3-6 */ private void lockRelease(JedisLock lock, Jedis jedis) { if (lock != null) { try { lock.release();// 则解锁 } catch (Exception e) { } } if (jedis != null) { try { pool.returnResource(jedis);// 还到连接池里 } catch (Exception e) { } } logger.info("release logck,lockKey={},timeoutMsecs={},expireMsecs={}", lockKey, timeoutMsecs, expireMsecs); } public static JedisPool getPool() { return pool; } public static synchronized void setPool(JedisPool pool) { SimpleLock.pool = pool; } }
2.3 测试
package com.chinaso.phl.concurrent; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.JedisPool; /** * @author com from network * @date 2014-3-13 */ public class SimpleLockTest { /** * @param args * @author com from network * @date 2014-3-13 */ public static void main(String[] args) { GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); final JedisPool pool = new JedisPool(poolConfig, "192.168.142.237", 6379, 3000); //最后一个参数为密码 SimpleLock.setPool(pool);//只需要初始化一次 String key = "test"; SimpleLock lock = new SimpleLock(key); lock.wrap(new Runnable() { @Override public void run() { //此处代码是锁上的 System.out.println(111); } }); } }