Redis+AQS

前言

對于java的單進程應用來說,存在資源競爭的場景可以使用synchronized關鍵字和Lock來對資源進行加鎖,使整個操作具有原子性。但是對于多進程或者分布式的應用來說,上面提到的鎖不共享,做不到互相通訊,所以就需要分布式鎖來解決問題了。
廢話不多說,直接進入正題,下面結合AQS和Redis來實現分布式鎖。

代碼中大部分都是參考ReentrantLock來實現的,所以讀者可以先去了解一下ReentranLock和AQS
參閱:
http://www.importnew.com/27477.html
http://cmsblogs.com/?p=2210


加鎖

 @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,成功!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386536.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386536.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386536.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Laravel 除了首頁能正常訪問,其它頁面均404

在寶塔系統上通過一鍵源碼配置laravel框架后&#xff0c;發現除了首頁能夠正常訪問&#xff0c;其它的頁面均返回的404&#xff0c;后經過比對已經網上查資料,發現是nginx的配置文件出了問題 1.找到配置文件 寶塔系統地址&#xff1a;/www/server/panel/vhost/nginx 2.在新建網…

disruptor 介紹

一、背景 1.來源 Disruptor是英國外匯交易公司LMAX開發的一個高性能隊列&#xff0c;研發的初衷是解決內部的內存隊列的延遲問題&#xff0c;而不是分布式隊列。基于Disruptor開發的系統單線程能支撐每秒600萬訂單&#xff0c;2010年在QCon演講后&#xff0c;獲得了業界關注。…

算法題+JVM+自定義View,詳細的Android學習指南

前言 想要成為一名優秀的Android開發&#xff0c;你需要一份完備的知識體系&#xff0c;在這里&#xff0c;讓我們一起成長為自己所想的那樣~。 學算法真的很痛苦&#xff0c;雖然大數據現在很火&#xff0c;但找到適合自己定位的職業也未嘗不是一種合理選擇。 投百度的經歷非…

用過的前端插件合集

用過的前端插件合集 FontAwesome字體 Font Awesome詳細用法參見上述站點的Examples。 SweetAlert系列 SweetAlertSweetAlert2SweetAlert 到 SweetAlert2 升級指南示例&#xff1a; 基本使用&#xff1a; swal("標題","內容","success);使用SweetAlert…

CAS和AQS

CAS 全稱&#xff08;Compare And Swap&#xff09;,比較交換 Unsafe類是CAS的核心類&#xff0c;提供硬件級別的原子操作。 // 對象、對象的地址、預期值、修改值 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);缺點&#xf…

系統盤點Android開發者必須掌握的知識點,全網瘋傳

最近在知乎上&#xff0c;有許多人在邀請我去回答“Android前景怎么樣、是不是要涼了、是不是應該考慮要轉行&#xff1f;”等一系列的問題。 想著可能有很多人都有這樣的擔心&#xff0c;于是就趕緊寫篇文章&#xff0c;來跟你們談下Android開發的前景到底怎么樣&#xff1f;…

CountDownLatch的理解和使用 多線程同步器

CountDownLatch的理解和使用 在筆者想要了解Thrift時候&#xff0c;找到一個博主寫的系統間通信技術的架構設計&#xff0c;在了解和學習的過程中遇到很多小問題和基礎知識&#xff0c;自己還是不夠清楚&#xff0c;就查詢和總結下。 因為筆者也都是從網上找的一些資料&#…

數據庫操作DDL

show database; 查看所有數據庫 drop database db_name; 刪除數據庫 create database db_name;創建數據庫 一個數據庫對應一個文件夾 create database if not exists db_name; show warnings; 查看所有警告 show create databae db_name;查看創建的數據庫 create database if n…

細數Android開發者的艱辛歷程,已拿offer附真題解析

籠統來說&#xff0c;中年程序員容易被淘汰的原因其實不外乎三點。 1、輸出能力已到頂點。這個人奮斗十來年了&#xff0c;依舊碌碌無為&#xff0c;很明顯這人的天花板就這樣了&#xff0c;說白了&#xff0c;天賦就這樣。 2、適應能力越來越差。年紀大&#xff0c;有家庭&…

原子操作類AtomicInteger詳解

為什么需要AtomicInteger原子操作類&#xff1f; 對于Java中的運算操作&#xff0c;例如自增或自減&#xff0c;若沒有進行額外的同步操作&#xff0c;在多線程環境下就是線程不安全的。num解析為numnum1&#xff0c;明顯&#xff0c;這個操作不具備原子性&#xff0c;多線程并…

移動端Rem之講解總結

日媽常說的H5頁面&#xff0c;為啥叫H5頁面嘛&#xff0c;不就是手機上展示的頁面嗎&#xff1f;那是因為啊手機兼容所有html5新特性&#xff0c;所以跑在手機上的頁面也叫h5頁面&#xff0c;跨平臺&#xff08;安裝ios),基于webview&#xff0c;它就是終端開發的一個組件&…

終于有人把安卓程序員必學知識點全整理出來了,送大廠面經一份!

除了Bug&#xff0c;最讓你頭疼的問題是什么&#xff1f;單身&#xff1f;禿頭&#xff1f;996?面試造火箭&#xff0c;工作擰螺絲&#xff1f; 作為安卓開發者&#xff0c;除了Bug&#xff0c;經常會碰到下面這些問題&#xff1a; 應用卡頓&#xff0c;丟幀&#xff0c;屏幕畫…

ABA問題

CAS&#xff1a;對于內存中的某一個值V&#xff0c;提供一個舊值A和一個新值B。如果提供的舊值V和A相等就把B寫入V。這個過程是原子性的。 CAS執行結果要么成功要么失敗&#xff0c;對于失敗的情形下一班采用不斷重試。或者放棄。 ABA&#xff1a;如果另一個線程修改V值假設原…

mq引入以后的缺點

系統可用性降低? 一旦mq不能使用以后,系統A不能發送消息到mq,系統BCD無法從mq中獲取到消息.整個系統就崩潰了. 如何解決: 系統復雜程度增加? 加入mq以后,mq引入來的問題很多,然后導致系統的復雜程度增加. 如何解決 系統的一致性降低? 有人給系統A發送了一個請求,本來這個請求…

網易云的朋友給我這份339頁的Android面經,成功入職阿里

IT行業的前景 近幾年來&#xff0c;大數據、人工智能AI、物聯網等一些技術不斷發展&#xff0c;也讓人們看到了IT行業的繁榮與良好的前景。越來越多的高校學府加大了對計算機的投入&#xff0c;設立相應的熱門專業來吸引招生。當然也有越來越多的人選擇從事這個行業&#xff0…

AQS相關邏輯解析

關心QPS TPS 如何讓線程停留在lock 1、競爭鎖-(拿到鎖的線程、沒拿到鎖的線程) 臨界區的資源&#xff08;static redis 數據庫變量 配置中心config zookeeper&#xff09;大家共享都可以獲得的資源 臨界區沒拿到鎖的未拿到鎖線程進行停留 2、怎么讓線程停留在Lock方法里 …

git介紹和常用操作

轉載于:https://www.cnblogs.com/kesz/p/11124423.html

網易云的朋友給我這份339頁的Android面經,滿滿干貨指導

想要成為一名優秀的Android開發&#xff0c;你需要一份完備的知識體系&#xff0c;在這里&#xff0c;讓我們一起成長為自己所想的那樣~。 25%的面試官會在頭5分鐘內決定面試的結果60%的面試官會在頭15分鐘內決定面試的結果 一般來說&#xff0c;一場單面的時間在30分鐘左右&…

synchronized 和Lock區別

synchronized實現原理 Java中每一個對象都可以作為鎖&#xff0c;這是synchronized實現同步的基礎&#xff1a; 普通同步方法&#xff0c;鎖是當前實例對象靜態同步方法&#xff0c;鎖是當前類的class對象同步方法塊&#xff0c;鎖是括號里面的對象 當一個線程訪問同步代碼塊…

美團安卓面試,難道Android真的涼了?快來收藏!

我所接觸的Android開發者&#xff0c;百分之九十五以上 都遇到了以下幾點致命弱點&#xff01; 如果這些問題也是阻止你升職加薪&#xff0c;跳槽大廠的阻礙。 那么我確信可以幫你突破瓶頸&#xff01; 1.開發者的門越來越高&#xff1a; 小廠的機會少了&#xff0c;大廠…