seata-tcc簡單使用
併發程式設計
記憶體模型
Java 記憶體模型是一個規範。規定一個執行緒如何、何時可以看到一個共享變數由其他執行緒修改後的值以及在必須時如何的同步訪問共享變數,執行緒之間的通訊必須經過主記憶體(??存在疑問)
資料儲存
記憶體分配
heep 堆
- 由垃圾回收機制負責回收
- 動態分配大小
- 存取數獨較慢
- 物件實列
- 一個物件的成員變數可能跟隨物件儲存在堆上(物件實列)
stack 棧
- 存取數度較快 次於暫存器
- 棧的資料可以共享?????
- 棧的大小和生存期必須的確定的
- 儲存基本型別的變數(int,short,byte…)、物件控制代碼
- 執行緒棧中儲存:呼叫棧、本地變數
- 靜態成員變數
同步操作(八步)
- Lock 鎖定作用於駐記憶體中的變數,將其標記為1條執行緒獨佔狀態 (一個執行緒可對一個變數多次加鎖,但必須執行相同次數的解鎖)
- Read 將主記憶體中變數的值讀取到工作記憶體(每個執行緒有自己的工作記憶體)中
- load 將讀取到工作記憶體中的值複製到 工作副本中(每個執行緒都有共享變數自己的副本)
- use 每個執行緒使用的是自己工作副本中的值
- Assign 將修改後的值賦值工作副本中
- Store 將工作副本中的值傳遞到主記憶體中
- Write 將工作副本傳遞過來的值寫回到主記憶體中
- Unlock 解鎖
同步資料規則
- 不允許 (Read,load) 和 (Store,Write) 單一出現,但可以不連續執行。
- 不允許一個執行緒丟棄掉最近一次的 Assign 操作,當資料在工作副本中發生變化後必須同步到主記憶體中不允許丟棄調
- 工作副本中的資料必須發生變化才能同步回主記憶體
- 對於共享變數必須對其進行 Read,load 操作後才能對起進行使用
- 一個變數在同一時刻只能有1個執行緒對其進行lock操作(統一時刻只有1個執行緒會lock成功)
- 當1個執行緒執行lock變臉成功後,會將工作記憶體中的改變數副本清楚重新執行 Read,load 操作
- 當unlock解鎖一個變數時必須將工作副本中的值 Write 回主記憶體,才能執行unlock
- 特殊規則:final 修飾的變數不允許修改
執行緒安全
原子性
atomic 原子操作包
AtomicInteger
原子的方式去操作 int 型別資料
//原子的方式增加1
getAndIncrement();
incrementAndGet()
//原子的方式遞減1
decrementAndGet()
...... 提供了一些列的原子操作
核心實現
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//核心方法 -----由java底層實現 compareAndSwap 系列方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
LongAdder
允許將64位的讀寫操作拆分成3個32位的操作,將核心資料value分離成一個數組,每次執行緒對每個陣列進行操作, 當前資料的值為陣列的和
優點 :分散分發點減少迴圈跟新的次數
確定:當在統計時如果有併發跟新會造成統計的資料有誤差
AtomicReferenceFieldUpdater
原子性的更新某個類上的某個欄位
static AtomicReferenceFieldUpdater<Test2,Integer> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Test2.class,Integer.class, "qwe");
Test2 test2 = new Test2();
referenceFieldUpdater.compareAndSet(test2,0,100);
System.out.println(test2.qwe);
AtomicStampedReference
解決 ABA 問題 stamp 多維護一個欄位,保持 stamp 為最新的不會回覆的資料
synchronized
synchronized 的作用範圍取決於 synchronized 獲取的鎖的作用域
- 當 synchronized 方法被繼承時候子類 不能繼承該方法 的 synchronized 機制
lock(自己研究哈)
可見性
synchronized
- 執行緒解鎖前,必須把共享變數的最新值重新整理到主記憶體
- 執行緒加鎖時,將清空工作記憶體中共享變數的值,從而使用共享變數時需要從出記憶體中重新讀取最新的值(加鎖和解鎖是同一把鎖)
volatile
- 對於 volatile 變數的寫操作時,會在**寫操作後**加入一天store 屏障指令,將本地記憶體中的共享變數值重新整理到主記憶體
- 對於volatile 變數的讀操作,會在**讀操作前** 加入一條load屏障指令,從主記憶體 中讀取共享變數
適用狀態表計量
有序性
java記憶體模型中,允許編譯器和處理器對指令進行**重排序,但是重排序過程不會影響到單執行緒程式的執行,卻會影響到多執行緒併發執行的正確性**,volatile 關鍵字可以禁止指令重排
程式次序規則:
-
一個執行緒內,按程式碼順序,前面的程式碼操作優先於後面的程式碼(單執行緒)
-
一個unlock操作先行發生於後面對同一個鎖的lock操作
-
volatile變數:對一個變數的寫操作先行發生於後面對這個變臉的讀操作(volatile插入屏障指令對於同時發生的讀寫操作進行排序使寫操作發生在前面)
執行緒安全釋出物件
錯誤的釋出
-
釋出物件:使一個物件能夠被當前範圍之外的程式碼所使用
對於內部states雖然為私有但是外部卻可以通過get獲取states地址的引用從而修改他
-
物件逸出:一種錯誤的釋出。當一個物件還沒有構建完成時被其他執行緒所見
- Escape未被初始化完成就被 InerClass 中的使用
@Slf4j public class Escape { private int thisCanEscape = 1; public Escape() { new InerClass(); } private class InerClass { public InerClass() { log.info("{}",Escape.this.thisCanEscape); } } public static void main(String[] args) { new Escape(); } }
正確的釋出
-
將物件放入 volatile 域中防治指令重排(1,2,3不賺可能發生指令重排序)
-
在靜態域中釋出物件
-
通過枚舉發布
public class Demo { private Demo() { } public static Demo getDemo() { return DemoEnum.INSTANCE.getInstance(); } private enum DemoEnum { INSTANCE; private Demo demo; DemoEnum() { demo = new Demo(); } public Demo getInstance() { return demo; } } }
不可變物件
-
Collections 下unmodifiable方法將集合變為不不改變,不允許修改新增等
執行緒封閉
同步容器
- ArrayList -> Vector,Stack(容器中的方法被synchronized同步)
- Vector執行緒相對安全一些
- Stack繼承Vector 資料結構為 棧
- HashMap -> HashTable(key,valeu 不能為空null)
- Collections.synchronizedXXX(List,Set,Map)
同步容器的不當操作
private static java.util.List<Integer> vector = new Vector<>();
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Thread thread1 = new Thread(() -> {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < vector.size(); i++) {
// 例如thread2想獲取i=9的元素的時候,thread1將i=9的元素移除了,導致陣列越界
vector.get(i);
// log.info("{}", integer);
}
});
thread1.start();
thread2.start();
}
}
併發容器
-
CopyOnWriteArrayList
- 新增元素時複製一個新的陣列,在新的陣列上寫操作然後將原來的list指向現在的list,因為每次新增都需要複製操作所以適合**讀多寫少**
當我們往一個容器新增元素的時候,不直接往當前容器新增,而是先將當前容器進行 Copy,複製出一個新的容器,然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。
這時候會丟擲來一個新的問題,也就是資料不一致的問題。如果寫執行緒還沒來得及寫會記憶體,其他的執行緒就會讀到了髒資料。
這就是CopyOnWriteArrayList 的思想和原理。就是拷貝一份寫。所以使用條件也很侷限,那就是在讀多寫少的情況下比較好。
-
CopyOnWriteArraySet
- 底層實現類似CopyOnWriteArrayList,使用
-
ConcurrentSkipListSet
-
ConcurrentHashMap(重要)
-
ConcurrentSkipListMap
AQS
AbstractQueuedLongSynchronizer
執行緒相關類
CountDownLatch
阻塞執行緒 等待子執行緒 完成後釋放阻塞
//定義10個字執行緒
final CountDownLatch countDownLatch = new CountDownLatch(10);
//遞減鎖存器的計數,如果計數到達零,則釋放所有等待的執行緒
new Thread(()->{
//TODO Do some thing
countDownLatch.countDown();
}).start();
//使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷。
countDownLatch.await();
Semaphore
控制:訊號量最多允許多少個執行緒執行
//最多允許10個執行緒同時執行
final Semaphore semaphore = new Semaphore(10);
new Thread(()->{
//從此訊號量獲取一個許可,在提供一個許可前一直將執行緒阻塞(如果獲取不到將一直阻塞執行緒)
semaphore.acquire();
//TODO Do some thing
.......
//釋放一個許可,將其返回給訊號量
semaphore.release();
}).start();