四。方案2,基于Redis的分布式锁
- /**
- * 分布式锁工厂类
- */
- public class RedisLockUtil {
- private static final Logger logger = Logger.getLogger(RedisLockUtil.class);
- private static Object schemeLock = new Object();
- private static Map<String, RedisLockUtil> instances = new ConcurrentHashMap();
- public static RedisLockUtil getInstance(String schema) {
- RedisLockUtil u = instances.get(schema);
- if (u == null) {
- synchronized (schemeLock) {
- u = instances.get(schema);
- if (u == null) {
- LockObserver lo = new LockObserver(schema);
- u = new RedisLockUtil(schema, lo);
- instances.put(schema, u);
- }
- }
- }
- return u;
- }
- private Object mutexLock = new Object();
- private Map<String, Object> mutexLockMap = new ConcurrentHashMap();
- private Map<String, RedisReentrantLock> cache = new ConcurrentHashMap<String, RedisReentrantLock>();
- private DelayQueue<RedisReentrantLock> dq = new DelayQueue<RedisReentrantLock>();
- private AbstractLockObserver lo;
- public RedisLockUtil(String schema, AbstractLockObserver lo) {
- Thread th = new Thread(lo);
- th.setDaemon(false);
- th.setName("Lock Observer:"schema);
- th.start();
- clearUselessLocks(schema);
- this.lo = lo;
- }
- public void clearUselessLocks(String schema) {
- Thread th = new Thread(new Runnable() {
- @Override
- public void run() {
- while (!SystemExitListener.isOver()) {
- try {
- RedisReentrantLock t = dq.take();
- if (t.clear()) {
- String key = t.getKey();
- synchronized (getMutex(key)) {
- cache.remove(key);
- }
- }
- t.resetCleartime();
- } catch (InterruptedException e) {
- }
- }
- }
- });
- th.setDaemon(true);
- th.setName("Lock cleaner:"schema);
- th.start();
- }
- private Object getMutex(String key) {
- Object mx = mutexLockMap.get(key);
- if (mx == null) {
- synchronized (mutexLock) {
- mx = mutexLockMap.get(key);
- if (mx == null) {
- mx = new Object();
- mutexLockMap.put(key, mx);
- }
- }
- }
- return mx;
- }
- private RedisReentrantLock getLock(String key, boolean addref) {
- RedisReentrantLock lock = cache.get(key);
- if (lock == null) {
- synchronized (getMutex(key)) {
- lock = cache.get(key);
- if (lock == null) {
- lock = new RedisReentrantLock(key, lo);
- cache.put(key, lock);
- }
- }
- }
- if (addref) {
- if (!lock.incRef()) {
- synchronized (getMutex(key)) {
- lock = cache.get(key);
- if (!lock.incRef()) {
- lock = new RedisReentrantLock(key, lo);
- cache.put(key, lock);
- }
- }
- }
- }
- return lock;
- }
- public void reset() {
- for (String s : cache.keySet()) {
- getLock(s, false).unlock();
- }
- }
- /**
- * 尝试加锁
- * 如果当前线程已经拥有该锁的话,直接返回,表示不用再次加锁,此时不应该再调用unlock进行解锁
- *
- * @param key
- * @return
- * @throws Exception
- * @throws InterruptedException
- * @throws KeeperException
- */
- public LockStat lock(String key) {
- return lock(key, -1);
- }
- public LockStat lock(String key, int timeout) {
- RedisReentrantLock ll = getLock(key, true);
- ll.incRef();
- try {
- if (ll.isOwner(false)) {
- ll.descrRef();
- return LockStat.NONEED;
- }
- if (ll.lock(timeout)) {
- return LockStat.SUCCESS;
- } else {
- ll.descrRef();
- if (ll.setCleartime()) {
- dq.put(ll);
- }
- return null;
- }
- } catch (LockNotExistsException e) {
- ll.descrRef();
- return lock(key, timeout);
- } catch (RuntimeException e) {
- ll.descrRef();
- throw e;
- }
- }
- public void unlock(String key, LockStat stat) {
- unlock(key, stat, false);
- }
- public void unlock(String key, LockStat stat, boolean keepalive) {
- if (stat == null) return;
- if (LockStat.SUCCESS.equals(stat)) {
- RedisReentrantLock lock = getLock(key, false);
- boolean candestroy = lock.unlock();
- if (candestroy && !keepalive) {
- if (lock.setCleartime()) {
- dq.put(lock);
- }
- }
- }
- }
- public static enum LockStat {
- NONEED,
- SUCCESS
- }
- }
- /**
- * 分布式锁本地代理类
- */
- public class RedisReentrantLock implements Delayed {
- private static final Logger logger = Logger.getLogger(RedisReentrantLock.class);
- private ReentrantLock reentrantLock = new ReentrantLock();
- private RedisLock redisLock;
- private long timeout = 3 * 60;
- private CountDownLatch lockcount = new CountDownLatch(1);
- private String key;
- private AbstractLockObserver observer;
- private int ref = 0;
- private Object refLock = new Object();
- private boolean destroyed = false;
- private long cleartime = -1;
- public RedisReentrantLock(String key, AbstractLockObserver observer) {
- this.key = key;
- this.observer = observer;
- initWriteLock();
- }
- public boolean isDestroyed() {
- return destroyed;
- }
- private synchronized void initWriteLock() {
- redisLock = new RedisLock(key, new LockListener() {
- @Override
- public void lockAcquired() {
- lockcount.countDown();
- }
- @Override
- public long getExpire() {
- return 0;
- }
- @Override
- public void lockError() {
- /*synchronized(mutex){
- mutex.notify();
- }*/
- lockcount.countDown();
- }
- }, observer);
- }
- public boolean incRef() {
- synchronized (refLock) {
- if (destroyed) return false;
- ref;
- }
- return true;
- }
- public void descrRef() {
- synchronized (refLock) {
- ref--;
- }
- }
- public boolean clear() {
- if (destroyed) return true;
- synchronized (refLock) {
- if (ref > 0) {
- return false;
- }
- destroyed = true;
- redisLock.clear();
- redisLock = null;
- return true;
- }
- }
- public boolean lock(long timeout) throws LockNotExistsException {
- if (timeout <= 0) timeout = this.timeout;
- //incRef();
- reentrantLock.lock();//多线程竞争时,先拿到第一层锁
- if (redisLock == null) {
- reentrantLock.unlock();
- //descrRef();
- throw new LockNotExistsException();
- }
- try {
- lockcount = new CountDownLatch(1);
- boolean res = redisLock.trylock(timeout);
- if (!res) {
- lockcount.await(timeout, TimeUnit.SECONDS);
- //mutex.wait(timeout*1000);
- if (!redisLock.doExpire()) {
- reentrantLock.unlock();
- return false;
- }
- }
- return true;
- } catch (InterruptedException e) {
- reentrantLock.unlock();
- return false;
- }
- }
- public boolean lock() throws LockNotExistsException {
- return lock(timeout);
- }
- public boolean unlock() {
- if (!isOwner(true)) {
- try {
- throw new RuntimeException("big ================================================ error.key:"key);
- } catch (Exception e) {
- logger.error("err:"e, e);
- }
- return false;
- }
- try {
- redisLock.unlock();
- reentrantLock.unlock();//多线程竞争时,释放最外层锁
- } catch (RuntimeException e) {
- reentrantLock.unlock();//多线程竞争时,释放最外层锁
- throw e;
- } finally {
- descrRef();
- }
- return canDestroy();
- }
- public boolean canDestroy() {
- synchronized (refLock) {
- return ref <= 0;
- }
- }
- <a href="http://www.nbso.ca/">
- nbso online
- casino reviews
- </a>
- public String getKey() {
- return key;
- }
- public void setKey(String key) {
- this.key = key;
- }
- public boolean isOwner(boolean check) {
- synchronized (refLock) {
- if (redisLock == null) {
- logger.error("reidsLock is null:key="key);
- return false;
- }
- boolean a = reentrantLock.isHeldByCurrentThread();
- boolean b = redisLock.isOwner();
- if (check) {
- if (!a || !b) {
- logger.error(key";a:"a";b:"b);
- }
- }
- return a && b;
- }
- }
- public boolean setCleartime() {
- synchronized (this) {
- if (cleartime > 0) return false;
- this.cleartime = System.currentTimeMillis() 10 * 1000;
- return true;
- }
- }
- public void resetCleartime() {
- synchronized (this) {
- this.cleartime = -1;
- }
- }
- @Override
- public int compareTo(Delayed object) {
- if (object instanceof RedisReentrantLock) {
- RedisReentrantLock t = (RedisReentrantLock) object;
- long l = this.cleartime - t.cleartime;
- if (l > 0) return 1; //比当前的小则返回1,比当前的大则返回-1,否则为0
- else if (l < 0) return -1;
- else return 0;
- }
- return 0;
- }
- @Override
- public long getDelay(TimeUnit unit) {
- long d = unit.convert(cleartime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
- return d;
- }
- }
- /**
- * 使用Redis实现的分布式锁
- * 基本工作原理如下:
- * 1. 使用setnx(key,时间戮 超时),如果设置成功,则直接拿到锁
- * 2. 如果设置不成功,获取key的值v1(它的到期时间戮),跟当前时间对比,看是否已经超时
- * 3. 如果超时(说明拿到锁的结点已经挂掉),v2=getset(key,时间戮 超时 1),判断v2是否等于v1,如果相等,加锁成功,否则加锁失败,等过段时间再重试(200MS)
- */
- public class RedisLock implements LockListener {
- private String key;
- private boolean owner = false;
- private AbstractLockObserver observer = null;
- private LockListener lockListener = null;
- private boolean waiting = false;
- private long expire;//锁超时时间,以秒为单位
- private boolean expired = false;
- public RedisLock(String key, LockListener lockListener, AbstractLockObserver observer) {
- this.key = key;
- this.lockListener = lockListener;
- this.observer = observer;
- }
- public boolean trylock(long expire) {
- synchronized (this) {
- if (owner) {
- return true;
- }
- this.expire = expire;
- this.expired = false;
- if (!waiting) {
- owner = observer.tryLock(key, expire);
- if (!owner) {
- waiting = true;
- observer.addLockListener(key, this);
- }
- }
- return owner;
- }
- }
- public boolean isOwner() {
- return owner;
- }
- public void unlock() {
- synchronized (this) {
- observer.unLock(key);
- owner = false;
- }
- }
- public void clear() {
- synchronized (this) {
- if (waiting) {
- observer.removeLockListener(key);
- waiting = false;
- }
- }
- }
- public boolean doExpire() {
- synchronized (this) {
- if (owner) return true;
- if (expired) return false;
- expired = true;
- clear();
- }
- return false;
- }
- @Override
- public void lockAcquired() {
- synchronized (this) {
- if (expired) {
- unlock();
- return;
- }
- owner = true;
- waiting = false;
- }
- lockListener.lockAcquired();
- }
- @Override
- public long getExpire() {
- return this.expire;
- }
- @Override
- public void lockError() {
- synchronized (this) {
- owner = false;
- waiting = false;
- lockListener.lockError();
- }
- }
- }
- public class LockObserver extends AbstractLockObserver implements Runnable {
- private CacheRedisClient client;
- private Object mutex = new Object();
- private Map<String, LockListener> lockMap = new ConcurrentHashMap();
- private boolean stoped = false;
- private long interval = 500;
- private boolean terminated = false;
- private CountDownLatch doneSignal = new CountDownLatch(1);
- public LockObserver(String schema) {
- client = new CacheRedisClient(schema);
- SystemExitListener.addTerminateListener(new ExitHandler() {
- public void run() {
- stoped = true;
- try {
- doneSignal.await();
- } catch (InterruptedException e) {
- }
- }
- });
- }
- public void addLockListener(String key, LockListener listener) {
- if (terminated) {
- listener.lockError();
- return;
- }
- synchronized (mutex) {
- lockMap.put(key, listener);
- }
- }
- public void removeLockListener(String key) {
- synchronized (mutex) {
- lockMap.remove(key);
- }
- }
- @Override
- public void run() {
- while (!terminated) {
- long p1 = System.currentTimeMillis();
- Map<String, LockListener> clone = new HashMap();
- synchronized (mutex) {
- clone.putAll(lockMap);
- }
- Set<String> keyset = clone.keySet();
- if (keyset.size() > 0) {
- ConnectionFactory.setSingleConnectionPerThread(keyset.size());
- for (String key : keyset) {
- LockListener ll = clone.get(key);
- try {
- if (tryLock(key, ll.getExpire())) {
- ll.lockAcquired();
- removeLockListener(key);
- }
- } catch (Exception e) {
- ll.lockError();
- removeLockListener(key);
- }
- }
- ConnectionFactory.releaseThreadConnection();
- } else {
- if (stoped) {
- terminated = true;
- doneSignal.countDown();
- return;
- }
- }
- try {
- long p2 = System.currentTimeMillis();
- long cost = p2 - p1;
- if (cost <= interval) {
- Thread.sleep(interval - cost);
- } else {
- Thread.sleep(interval * 2);
- }
- } catch (InterruptedException e) {
- }
- }
- }
- /**
- * 超时时间单位为s!!!
- *
- * @param key
- * @param expire
- * @return
- */
- public boolean tryLock(final String key, final long expireInSecond) {
- if (terminated) return false;
- final long tt = System.currentTimeMillis();
- final long expire = expireInSecond * 1000;
- final Long ne = tt expire;
- List<Object> mm = client.multi(key, new MultiBlock() {
- @Override
- public void execute() {
- transaction.setnxObject(key, ne);
- transaction.get(SafeEncoder.encode(key));
- }
- });
- Long res = (Long) mm.get(0);
- if (new Long(1).equals(res)) {
- return true;
- } else {
- byte[] bb = (byte[]) mm.get(1);
- Long ex = client.deserialize(bb);
- if (ex == null || tt > ex) {
- Long old = client.getSet(key, new Long(ne 1));
- if (old == null || (ex == null && old == null) || (ex != null && ex.equals(old))) {
- return true;
- }
- }
- }
- return false;
- }
- public void unLock(String key) {
- client.del(key);
- }
- }
使用本方案实现的分布式锁,可以完美地解决锁重入问题;通过引入超时也避免了死锁问题;性能方面,笔者自测试结果如下:
500线程 tps = 35000
[root@DB1 benchtest-util]# target/benchtest/bin/TestFastRedis /data/config/util/config_0_11.properties lock 500 500000
线程总时间:6553466;平均:13.106932
实际总时间:13609; 平均:0.027218
TPS达到35000,比方案1强了整整一个数量级;
五。总结
本文介绍了两种分布式锁的实现方案;方案1最大的优势在于避免结点挂掉后导致的死锁;方案2最大的优势在于性能超强;在实际生产过程中,结合自身情况来决定最适合的分布式锁方案是架构师的必修课。