自适应网站模板下载,沉默是金歌曲原唱,成都住建局官网下载,一个企业网站的建设流程前言
对于java的单进程应用来说#xff0c;存在资源竞争的场景可以使用synchronized关键字和Lock来对资源进行加锁#xff0c;使整个操作具有原子性。但是对于多进程或者分布式的应用来说#xff0c;上面提到的锁不共享#xff0c;做不到互相通讯#xff0c;所以就需要分…前言
对于java的单进程应用来说存在资源竞争的场景可以使用synchronized关键字和Lock来对资源进行加锁使整个操作具有原子性。但是对于多进程或者分布式的应用来说上面提到的锁不共享做不到互相通讯所以就需要分布式锁来解决问题了。 废话不多说直接进入正题下面结合AQS和Redis来实现分布式锁。
代码中大部分都是参考ReentrantLock来实现的所以读者可以先去了解一下ReentranLock和AQS 参阅http://www.importnew.com/27477.htmlhttp://cmsblogs.com/?p2210 加锁 Overrideprotected boolean tryAcquire(int acquires) throws AcquireLockTimeoutException {final Thread current Thread.currentThread();int c getState();if (c 0) {if (!hasQueuedPredecessors() compareAndSetState(0, 1)) { // 标注1setExclusiveOwnerThread(current);// 如果是线程被中断失败的话返回false如果超时失败的话捕获异常return tryAcquireRedisLock(TimeUnit.MILLISECONDS.toNanos(redisLockTimeout));}//可重入} else if (current getExclusiveOwnerThread()) { //标注2int nextc c acquires;if (nextc 0) {throw new Error(Maximum lock count exceeded);}setState(nextc);return true;}return false;} 下面会把进程内的锁称为进程锁如果有更专业的描述方法的话欢迎指出。
对上面的步骤分析 1. 首先看标注1通过compareAndSetState获取到进程锁只有获取到进程锁才有资格去竞争redis锁 这样的好处就是对于同一个进程里面的所有加锁请求在某一个时刻只有一个请求能去请求获取redis锁有效降低redis的压力总的来说就是把部分竞争交给进程自己去解决了也就是先竞争进程锁。 2. 再看标注2能进行到这一步首先能确保已经获取了进程锁但是是否一定获取了redis锁了呢不一定所以在tryAcquireRedisLock的过程中如果抛出异常一定要保证使用finally代码块把进程锁释放掉避免误以为已经同时获取了进程锁和redis锁。
获取redis锁
private final boolean tryAcquireRedisLock(long nanosTimeout) {if (nanosTimeout 0L) {return false;}final long deadline System.nanoTime() nanosTimeout;int count 0;boolean interrupted false;Jedis jedis null;try {jedis redisHelper.getJedisInstance();while (true) {nanosTimeout deadline - System.nanoTime();if (nanosTimeout 0L) {throw new AcquireLockTimeoutException();}String value String.format(valueFormat, Thread.currentThread().getId());//避免系统宕机锁不释放设置过期时间String response jedis.set(lockKey, value, NX, PX, redisLockTimeout);if (OK.equals(response)) {//如果线程被中断同时也是失败的return !interrupted;}// 超过尝试次数if (count RETRY_TIMES nanosTimeout SPIN_FOR_TIMEOUT_THRESHOLD parkAndCheckInterrupt()) {interrupted true;}count;}} finally {redisHelper.returnResouce(jedis);}}final boolean parkAndCheckInterrupt() {LockSupport.parkNanos(TimeUnit.NANOSECONDS.toNanos(PARK_TIME));return Thread.interrupted();
} 分析 1. 为了避免获取redis锁的过程无休止的运行下去使用超时策略如果超时了直接返回失败 2. 如果还在有效时间内则通过自旋不断尝试获取锁如果超过了尝试次数暂时挂起让出时间片但是不可以挂起太长的时间几个时间片内为好。
解锁
//RedisDistributedLock.java
Override
public void unlock() {sync.unlock();
}//Sync.java
public void unlock() {release(1);
}Override
protected final boolean tryRelease(int releases) {int c getState() - releases;if (Thread.currentThread() ! getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free false;if (c 0) {Jedis jedis null;try {jedis redisHelper.getJedisInstance();String value String.format(valueFormat, Thread.currentThread().getId());jedis.eval(UNLOCK_SCRIPT, Arrays.asList(lockKey), Arrays.asList(value));} finally {redisHelper.returnResouce(jedis);}free true;setExclusiveOwnerThread(null);}setState(c);return free;
} 分析 1. 可以注意到value在加锁和解锁的过程都有这个value是用来标识锁的唯一性的避免别的进程误删了该锁。
private final UUID uuid UUID.randomUUID();
private final String valueFormat %d: uuid.toString(); 验证
Overridepublic void run() {SqlSession session MybatisHelper.instance.openSession(true);try {KeyGeneratorMapper generatorMapper session.getMapper(KeyGeneratorMapper.class);KeyFetchRecordMapper recordMapper session.getMapper(KeyFetchRecordMapper.class);while (true) {try {lock.lock();KeyGenerator keyGenerator generatorMapper.select(1);if (keyGenerator.getKey() MAX_KEY) {System.exit(0);}recordMapper.insert(new KeyFetchRecord(keyGenerator.getKey(), server));generatorMapper.increase(1, 1);session.commit();} catch (RuntimeException e) {e.printStackTrace();continue;} finally {lock.unlock();}}} finally {session.close();}} 开启5个进程每个进程5个线程进行获取一个key值获取到后加1然后记录到数据库这个过程不要是原子的因为把没有原子性的过程变成有原子性的过程才证明了这个锁的有效性。
结果如下 没有重复的key,成功