springboot高并发redis细粒度加锁(key粒度加锁)
本文探讨在web开发中如何解决并发访问带来的数据同步问题。
1、需求:
通过REST接口请求并发访问redis,例如:将key=fusor:${order_id} 中的值+1;
2、场景:
设想,多线程对key=fusor:${order_id}并发访问触发了竞态条件,例如两个线程同时发现key=fusor:${order_id}的值为5,然后并且+1回写6,这个时候就出现了问题,最终的值为6而不是7。
3、粗粒度锁:
这时候,普遍的做法是加锁,但是如果对整个访问redis的动作加锁,那么等于多个线程串行访问了!
4、细粒度加锁:
我们这里的做法是对key进行细粒度加锁,每个key拥有一把锁,只对key进行并发控制,key与key之间允许并发。
直接上代码
package com.xiaoju.dqa.fusor.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Random; import java.util.UUID; @Component public class SexyLockUtil<K> { private static Logger logger = LoggerFactory.getLogger(SexyLockUtil.class); @Autowired private SexyLocker<K> sexyLocker; public KeyLocker<K> getKeyLocker(K key) { return new KeyLocker<K>(sexyLocker, key); } public static class KeyLocker<K> { private UUID uuid = UUID.randomUUID(); private SexyLocker sexyLocker; private K key; public KeyLocker(SexyLocker sexyLocker, K key) { this.sexyLocker = sexyLocker; this.key = key; } public boolean lockWithRetry(int expireTime, int retryTimes) { Random random = new Random(); for (int i = 0; i < retryTimes; i++) { if (this.lock()) { return true; } else { try { Thread.sleep(random.nextInt(50)); } catch (InterruptedException e) { } } } return false; } public boolean lock() { try { this.sexyLocker.lock(key); return true; } catch (Exception e) { return false; } } public boolean unlock() { try { this.sexyLocker.unlock(key); return true; } catch (Exception e) { return false; } } } }
package com.xiaoju.dqa.fusor.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Semaphore; @Component public class SexyLocker<K> { private static Logger logger = LoggerFactory.getLogger(SexyLockUtil.class); private final ConcurrentMap<K, Semaphore> map = new ConcurrentHashMap<K, Semaphore>(); private final ThreadLocal<Map<K, LockInfo>> local = new ThreadLocal<Map<K, LockInfo>>() { @Override protected Map<K, LockInfo> initialValue() { return new HashMap<K, LockInfo>(); } }; public void lock(K key) { if (key == null) return; LockInfo info = local.get().get(key); if (info == null) { Semaphore current = new Semaphore(1); current.acquireUninterruptibly(); Semaphore previous = map.put(key, current); if (previous != null) previous.acquireUninterruptibly(); local.get().put(key, new LockInfo(current)); } else { info.lockCount++; } } public void unlock(K key) { if (key == null) return; LockInfo info = local.get().get(key); if (info != null && --info.lockCount == 0) { info.current.release(); map.remove(key, info.current); local.get().remove(key); } } public void lock(K[] keys) { if (keys == null) return; for (K key : keys) { lock(key); } } public void unlock(K[] keys) { if (keys == null) return; for (K key : keys) { unlock(key); } } private static class LockInfo { private final Semaphore current; private int lockCount; private LockInfo(Semaphore current) { this.current = current; this.lockCount = 1; } } }