使用雙非同步後,如何保證資料一致性?
來源:哪吒程式設計
一、前情提要
在上一篇文章中,我們使用雙非同步後,從 191s 最佳化到 2s,有個小夥伴在評論區問我,如何保證插入後資料的一致性呢?
很簡單,透過對比Excel檔案行數和入庫數量是否相等即可。
那麼,如何獲取非同步執行緒的返回值呢?
二、透過Future獲取非同步返回值
我們可以透過給非同步方法新增Future返回值的方式獲取結果。
FutureTask 除了實現 Future 介面外,還實現了 Runnable 介面。因此,FutureTask 可以交給 Executor 執行,也可以由呼叫執行緒直接執行FutureTask.run()。
1、FutureTask 是基於 AbstractQueuedSynchronizer實現的
AbstractQueuedSynchronizer簡稱AQS,它是一個同步框架,它提供通用機制來原子性管理同步狀態、阻塞和喚醒執行緒,以及 維護被阻塞執行緒的佇列。基於 AQS 實現的同步器包括:ReentrantLock、Semaphore、ReentrantReadWriteLock、 CountDownLatch 和 FutureTask。
基於 AQS實現的同步器包含兩種操作:
acquire,阻塞呼叫執行緒,直到AQS的狀態允許這個執行緒繼續執行,在FutureTask中,get()就是這個方法; release,改變AQS的狀態,使state變為非阻塞狀態,在FutureTask中,可以透過run()和cancel()實現。
2、FutureTask執行流程
執行@Async非同步方法; 建立新執行緒async-executor-X,執行Runnable的run()方法,(FutureTask實現RunnableFuture,RunnableFuture實現Runnable); 判斷狀態state;
如果未新建或者不處於AQS,直接返回; 否則進入COMPLETING狀態,執行非同步執行緒程式碼;
將自己從AQS佇列中移除; 然後喚醒next執行緒async-executor-2; 改變執行緒async-executor-1的state; 等待get()執行緒取值。
被喚醒 從AQS佇列中移除 喚醒next執行緒 改變非同步執行緒狀態
如果處於EXCEPTIONAL以上狀態,丟擲異常; 如果處於COMPLETING狀態,加入AQS佇列等待; 如果處於NORMAL狀態,返回結果;
3、get()方法執行流程
get()方法透過判斷狀態state觀測非同步執行緒是否已結束,如果結束直接將結果返回,否則會將等待節點扔進等待佇列自旋,阻塞住執行緒。
自旋直至非同步執行緒執行完畢,獲取另一邊的執行緒計算出結果或取消後,將等待佇列裡的所有節點依次喚醒並移除佇列。
如果state小於等於COMPLETING,表示任務還在執行中;
計算超時時間; 如果超時,則從等待佇列中移除等待節點WaitNode,返回當前狀態state; 阻塞佇列nanos毫秒。 如果已有等待節點WaitNode,將執行緒置空; 返回當前狀態; 如果執行緒被中斷,從等待佇列中移除等待節點WaitNode,丟擲中斷異常; 如果state大於COMPLETING; 如果任務正在執行,讓出時間片; 如果還未構造等待節點,則new一個新的等待節點; 如果未入佇列,CAS嘗試入隊; 如果有超時時間引數; 否則阻塞佇列;
如果執行完畢,返回結果; 如果大於等於取消狀態,則丟擲異常。
很多小朋友對讀原始碼,嗤之以鼻,工作3年、5年,還是沒認真讀過任何原始碼,覺得讀了也沒啥用,或者讀了也看不懂~
其實,只要把原始碼的執行流程透過畫圖的形式呈現出來,你就會幡然醒悟,原來是這樣的~
簡而言之:
1. 如果非同步執行緒還沒執行完,則進入CAS自旋;
2. 其它執行緒獲取結果或取消後,重新喚醒CAS佇列中等待的執行緒;
3. 再透過get()判斷狀態state;
4. 直至返回結果或(取消、超時、異常)為止。
三、FutureTask原始碼具體分析
1、FutureTask原始碼
透過定義整形狀態值,判斷state大小,這個思想很有意思,值得學習。
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
// 最初始的狀態是new 新建狀態
private volatile int state;
private static final int NEW = 0; // 新建狀態
private static final int COMPLETING = 1; // 完成中
private static final int NORMAL = 2; // 正常執行完
private static final int EXCEPTIONAL = 3; // 異常
private static final int CANCELLED = 4; // 取消
private static final int INTERRUPTING = 5; // 正在中斷
private static final int INTERRUPTED = 6; // 已中斷
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 任務還在執行中
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
// 執行緒被中斷,從等待佇列中移除等待節點WaitNode,丟擲中斷異常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 任務已執行完畢或取消
if (s > COMPLETING) {
// 如果已有等待節點WaitNode,將執行緒置空
if (q != null)
q.thread = null;
return s;
}
// 任務正在執行,讓出時間片
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 還未構造等待節點,則new一個新的等待節點
else if (q == null)
q = new WaitNode();
// 未入佇列,CAS嘗試入隊
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 如果有超時時間引數
else if (timed) {
// 計算超時時間
nanos = deadline - System.nanoTime();
// 如果超時,則從等待佇列中移除等待節點WaitNode,返回當前狀態state
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 阻塞佇列nanos毫秒
LockSupport.parkNanos(this, nanos);
}
else
// 阻塞佇列
LockSupport.park(this);
}
}
private V report(int s) throws ExecutionException {
// 獲取outcome中記錄的返回結果
Object x = outcome;
// 如果執行完畢,返回結果
if (s == NORMAL)
return (V)x;
// 如果大於等於取消狀態,則丟擲異常
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
}
2、將非同步方法的返回值改為Future<Integer>
,將返回值放到new AsyncResult<>();
中;
@Async("async-executor")
public void readXls(String filePath, String filename) {
try {
// 此程式碼為簡化關鍵性程式碼
List<Future<Integer>> futureList = new ArrayList<>();
for (int time = 0; time < times; time++) {
Future<Integer> sumFuture = readExcelDataAsyncFutureService.readXlsCacheAsync();
futureList.add(sumFuture);
}
}catch (Exception e){
logger.error("readXlsCacheAsync---插入資料異常:",e);
}
}
@Async("async-executor")
public Future<Integer> readXlsCacheAsync() {
try {
// 此程式碼為簡化關鍵性程式碼
return new AsyncResult<>(sum);
}catch (Exception e){
return new AsyncResult<>(0);
}
}
3、透過Future<Integer>.get()
獲取返回值:
public static boolean getFutureResult(List<Future<Integer>> futureList, int excelRow){
int[] futureSumArr = new int[futureList.size()];
for (int i = 0;i<futureList.size();i++) {
try {
Future<Integer> future = futureList.get(i);
while (true) {
if (future.isDone() && !future.isCancelled()) {
Integer futureSum = future.get();
logger.info("獲取Future返回值成功"+"----Future:" + future
+ ",Result:" + futureSum);
futureSumArr[i] += futureSum;
break;
} else {
logger.info("Future正在執行---獲取Future返回值中---等待3秒");
Thread.sleep(3000);
}
}
} catch (Exception e) {
logger.error("獲取Future返回值異常: ", e);
}
}
boolean insertFlag = getInsertSum(futureSumArr, excelRow);
logger.info("獲取所有非同步執行緒Future的返回值成功,Excel插入結果="+insertFlag);
return insertFlag;
}
4、這裡也可以透過新執行緒+Future獲取Future返回值
不過感覺多此一舉了,就當練習Future非同步取返回值了~
public static Future<Boolean> getFutureResultThreadFuture(List<Future<Integer>> futureList, int excelRow) {
ExecutorService service = Executors.newSingleThreadExecutor();
final boolean[] insertFlag = {false};
service.execute(new Runnable() {
public void run() {
try {
insertFlag[0] = getFutureResult(futureList, excelRow);
} catch (Exception e) {
logger.error("新執行緒+Future獲取Future返回值異常: ", e);
insertFlag[0] = false;
}
}
});
service.shutdown();
return new AsyncResult<>(insertFlag[0]);
}
獲取非同步執行緒結果後,我們可以透過新增事務的方式,實現Excel入庫操作的資料一致性。
但Future會造成主執行緒的阻塞,這個就很不友好了,有沒有更優解呢?
來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024420/viewspace-3004755/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 如何保證mongodb和資料庫雙寫資料一致性?MongoDB資料庫
- 如何保證快取與資料庫的雙寫一致性?快取資料庫
- 如何保證MySQL資料一致性MySql
- 如何保證快取(redis)與資料庫的雙寫一致性快取Redis資料庫
- 如何保證MySQL和Redis資料一致性?MySqlRedis
- 冗餘資料一致性,到底如何保證?
- 阿里面試題:如何保證快取與資料庫的雙寫一致性?阿里面試題快取資料庫
- Spark CommitCoordinator 保證資料一致性SparkMIT
- 資料庫和快取的一致性如何保證資料庫快取
- 如何保證快取和資料庫的一致性?快取資料庫
- Zookeeper 如何保證分散式系統資料一致性分散式
- 趣說 | 資料庫和快取如何保證一致性?資料庫快取
- 美團二面:Redis與MySQL雙寫一致性如何保證?RedisMySql
- 保證Redis和資料庫資料一致性的方法Redis資料庫
- MySQL是怎麼保證資料一致性的MySql
- 前後端API互動如何保證資料安全性?後端API
- 面試常問:如何保證Redis快取和資料庫的資料一致性NRXW面試Redis快取資料庫
- 針對靜默資料錯誤,如何採用DIX和DIF保證資料一致性?
- 面試重災區:怎麼保證快取與資料庫的雙寫一致性?面試快取資料庫
- HTTPS 如何保證資料傳輸安全HTTP
- Elasticsearch如何保證資料不丟失?Elasticsearch
- PHP 併發扣款,保證資料一致性(悲觀鎖)PHP
- MySQL半同步複製資料最終一致性驗證MySql
- 獲取雙非同步返回值時,如何保證主執行緒不阻塞?非同步執行緒
- 雙重保險——前端bootstrapValidator驗證+後臺MVC模型驗證前端bootMVC模型
- 【面試普通人VS高手系列】Redis和Mysql如何保證資料一致性面試RedisMySql
- 【對話ACE】第七期|雙11,如何保證資料庫穩定可靠?資料庫
- 【北亞資料恢復】如何保證LINUX執行FSCK後的資料不出問題?資料恢復Linux
- 資料庫同步利器 otter 雙A同步配置資料庫
- 遠端辦公如何保證資料安全?
- 第50篇 Redis與DB庫(持續化儲存)之間的資料雙寫一致性保證Redis
- 如何保證 Serverless 業務部署更新的一致性?Server
- Seata-AT 如何保證分散式事務一致性分散式
- Oracle Goldengate是如何保證資料有序和確保資料不丟失的?OracleGo
- 漸進式web應用開發-- 使用後臺同步保證離線功能(六)Web
- Spark Streaming使用Kafka保證資料零丟失SparkKafka
- ogg在異構資料庫實時雙向同步中如何防止資料死迴圈同步資料庫
- 美團二面:如何保證Redis與Mysql雙寫一致性?連續兩個面試問到了!RedisMySql面試