網站首頁 編程語言 正文
目錄
1. JMM模型
2. 并發和并行
3.并發三大特性
?3.1 可見性
3.2 上下文切換
?3.3 lock指令的作用
4. java并發知識體系
5. 計算機組成架構
6. CPU三級緩存架構
7.緩存一致性機制
7.1 總線裁決?
?7.2 緩存行填充問題
8.java可見性保證?
8.1 JVM層面的內存屏障和硬件層面的內存屏障
1. JMM模型
JMM內存模型是java虛擬機提出的一種規范,用來協調操作系統與硬件的各種差異,實現并發效果。
其中有8種原子指令:
1.lock:鎖定變量
2.unlock:解鎖鎖定的變量,當鎖定多次時需要解鎖同樣次數,鎖定與解鎖必須成對出現
3. read,load:從主內存讀取變量值加載到本地內存,兩個原子指令必須順序執行
4. use:將本地內存中的變量值給執行引擎,隨時給線程使用
5. assign:將執行引擎的值賦值給本地內存
6. store,write:從本地內存存儲變量值并寫入到主內存,兩個原子指令必須順序執行
2. 并發和并行
并行:在同一時刻有多條指令執行
?
?并發:在同一時刻只能有一個指令執行,并伴隨指令之間的切換時間很短。大概在10-100ms之間,用戶無法感知。
并發解決問題:
????????同步:一個任務依賴另外一個任務的執行完成才能執行。
????????互斥:一個任務的資料不能被其他任務使用。
????????分工:一個很大的任務量分解為更多小的任務最后進行重組得出答案。
?
3.并發三大特性
原子性:單個指令不可分割
可見性:對變量的寫操作,在任務線程中都能看到最后寫入的值
有序性:程序編寫順序和執行順序一致,即使在多線程中也要保持此順序一致
保證可見性的操作:
1.內存屏障:
volatile,內存屏障(jdk8中UnsafeFactory.getUnsafe().storeFence();調用內存屏障)
synchronized,lock,final
2. 上下文切換:Thread.yieled
實際上在底層C++都是調用匯編指令調用內存屏障保證,如果需要查看匯編指令可以在VM參數中加入
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp如果缺少hsdis-amd64.dll文件需要下載可以在控制臺打印匯編指令,通過搜索lock關鍵字。
?3.1 可見性
在一個線程變量可以被另外一個線程變量讀取到最后的值。
可見性的幾種方法:
1. volatile修飾
2.內存屏障
3.使用synchronized修飾
4.使用Lock保證可見性
5. 使用final關鍵字保證可見性
比較保守的保證可見性的方法是使用volatile關鍵字修飾
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;
/**
* 可見性測試 七種實現可見性的方法
*/
public class VolatileTest {
private boolean flag = true;
// private volatile boolean flag = true;//第一種方法使用volatile
private int count = 0;
// private volatile int count = 0;//第六種方法給count值聲明volatile
// private Integer count = 0;//第七種方法給count值聲明Integer,底層使用final修飾保證可見性
public void changgeFlag(){
flag = false;
System.out.println(Thread.currentThread().getName()+"flag:"+flag);
}
public void load() throws Exception{
System.out.println(Thread.currentThread().getName()+"開始執行");
while (flag){
count++;
//第二種方法內存屏障保證可見性,不建議使用
// Unsafe.getUnsafe().storeFence();//報錯:java.lang.SecurityException: Unsafe
//1.最簡單的使用方式是基于反射獲取Unsafe實例
// Field f = Unsafe.class.getDeclaredField("theUnsafe");
// f.setAccessible(true);
// Unsafe unsafe = (Unsafe) f.get(null);
// unsafe.storeFence();
//第三種方法 通過上下文切換讀取主內存的值保證
// Thread.yield();
//第四種保證可見性的方法 使用synchronized底層使用內存屏障保證
// System.out.println(count);
//第五種保證可見性的方法 發放許可的方式
// LockSupport.unpark(Thread.currentThread());
//第八種方法等待一段時間把本地內存緩存清除從主內存拿值
// waitLong(1000000);//1000ms
// waitLong(1000);//1ms 時間太短不會跳出循環
//第九種方法
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+"跳出循環:count="+count);
}
private static void waitLong(long interval){
long begin = System.nanoTime();//納秒值
long end;
do {
end = System.nanoTime();
}while(begin+interval > end);
}
public static void main(String[] args) throws Exception {
//兩個方法,第一個方法依靠flag執行死循環
VolatileTest v1 = new VolatileTest();
Thread thread = new Thread(()-> {
try {
v1.load();
} catch (Exception e) {
e.printStackTrace();
}
},"threadA");
thread.start();
//第二個對象改變flag值跳出死循環
Thread.sleep(1000);
Thread thread1 = new Thread(()->v1.changgeFlag(),"threadB");
thread1.start();
}
}
3.2 上下文切換
?上下文切換:一個線程執行一半被線程2搶占時間片,等線程2執行完以后再執行線程1。中間需要使用到PC-程序計數器指向到原來執行前的步驟。
?3.3 lock指令的作用
lock前綴指令的作用:將指令前的寫指令全部執行完,將緩存中的值全部失效。所有的值只能從主內存中讀取,達到可見性的目的
4. java并發知識體系
1.線程基礎
2.線程安全:同步,互斥,分工
3. 并發工具類
4. 并發設計模式
5. 計算機組成架構
偽代碼演示計算機計算過程:
x=3;
y=x+5;
1.PC->記錄執行代碼位置->比如y=x+5;這一行
2.load:registers->cache(未加載到X)->內存(X=3);->cache(x=3)->register;
3.add:ALU(y=3+5=8)->registers(y=8)->cache(y=8)->內存(y=8)
?
6. CPU三級緩存架構
CPU現在一般有三級緩存架構:兩個L1Cache,一個L2 Cache,一個多核心共享的L3 Cache
為什么要引入三級緩存:內存存取周期比CPU要慢很多,如果中間有個緩存,里面有的值可以直接取,大大減少CPU的等待時間。
局部性原理:在CPU訪問存儲設備時,無論是存儲數據或指令都趨向于聚集在一塊連續的區域。分為兩種:時間局部性和空間局部性。
時間局部性:一個信息被訪問,那么在近期它可能還會被訪問。
空間局部性:如果一個存儲器的位置被引用,那么它附近的位置也會被引用。
?
?
7.緩存一致性機制
?計算機中為了解決緩存不一致的問題,有兩種機制保證緩存一致性:窺探機制和基于目錄的機制。
窺探機制(<=64核處理器使用):每個請求都必須廣播到系統中的所有節點,速度很快,但是很耗帶寬。
基于窺探機制有兩種實現方式:write-invalidate(寫失效),write-update(少用,考慮帶寬問題)
write-invalidate(寫失效):當一個處理器中的緩存值更改后會告知其他處理器的緩存值失效,保證只能有一個緩存值有效。常見的協議有MSI,MESI,MOSI,MOESI,MESIF
????????MESI協議:M-修改 E-獨占 S-共享 I-無效;
? ? ? ? 偽代碼演示X=5;X=3+X;協議過程如下:
? ? ? ? 1. 第一個線程:內存read->load指令->CPU高速緩存(X=5),此時緩存處于E獨占狀態
? ? ? ? 2. 第二個線程內存read->load指令->CPU高速緩存(X=5)->此時緩存處于S-共享狀態
????????3. 第一個線程內存->ALU(3+5=8=X)->CPU高速緩存(X=8)->此時緩存處于M-修改狀態,其他處理器緩存處于I-無效狀態
? ? ? ? 4.lock指令會保證緩存立即刷回主內存(X=8)
write-update(寫更新):當一個處理器中的緩存值更改后會更改其他處理器的緩存值,但要考慮帶寬問題,用的比較少。
緩存一致性無法保證的兩種場景:1.跨緩存行(比如X中的值大于64字節) 2.早期處理器沒有實現緩存一致性協議,總線鎖定,串行執行。
基于目錄的機制(>64核處理器使用):延遲更長,帶寬使用很少。
7.1 總線裁決?
當有多個處理器同時請求總線,總線會去判定其中一個處理器A勝出,處理器B只能等待A處理完成才能請求。
總線鎖定:當處理器A執行請求時,其他處理器都不能使用,總線鎖定是使用lock前綴指令和lock#信號實現
#linux服務器查看緩存塊大小
cd /sys/devices/system/cpu/cpu0/cache/index0
ls
[root@localhost index0]# cat coherency_line_size
#或者輸入以下命令查看cache_alignment參數:64
cat /proc/cpuinfo
?
?7.2 緩存行填充問題
緩存行默認只能存儲64個字節,如果不夠64位就可能使用volatile導致緩存一致性協議使其他緩存失效,導致只能不停從主內存讀取,為了解決這個問題,有兩種方法:
1.jdk7中第一種方法 避免偽共享: 緩存行填充7個long,每個long占用8個字節,加上x等于64個字節
2.第二種方法,避免偽共享: @Contended + ?jvm參數:-XX:-RestrictContended ?jdk8支持
import sun.misc.Contended;
/**
* 偽共享
*/
public class FalseSharingTest {
public static void main(String[] args) throws InterruptedException {
testPointer(new Pointer());
}
private static void testPointer(Pointer pointer) throws InterruptedException {
long start = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.x++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.y++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// 思考:x,y是線程安全的嗎?
System.out.println(pointer.x+","+pointer.y);
System.out.println(System.currentTimeMillis() - start);
}
}
class Pointer {
// 2.第二種方法,避免偽共享: @Contended + jvm參數:-XX:-RestrictContended jdk8支持
@Contended
volatile long x;
//1.jdk7中第一種方法 避免偽共享: 緩存行填充7個long,每個long占用8個字節,加上x等于64個字節
// long p1, p2, p3, p4, p5, p6, p7;
volatile long y;
}
?
8.java可見性保證?
1. 單線程程序不能保證內存可見性問題。java在編譯時:java-class->java指令序列->匯編指令->機器碼,在不影響最終結果的前提下會進行一個重排序,多線程無法保證。
正確使用同步的程序可以通過禁止編譯器和處理器的重排序保證內存可見性。
JMM不能保證所有線程都能看到一致的操作執行順序。
JMM不能保證在32位處理器對64位的long和double變量寫操作的原子性。
未正確同步的多線程不能保證。
匯編指令級的內存屏障一共四種:LoadLoad,LoadStore,StoreStore,StoreLoad,但X86處理器只有一種stroeLoad可以生效,其他三種都為空操作。
JVM層級的內存屏障:UnSafeFactory.getUnSafe().storeFence();這種調用內存屏障的方式jdk8以后可能會被舍棄,一般使用volatile保證內存可見性即可。
volatile內存語義:
1.可見性:對一個volatile修飾的變量,總能在多個線程中看到最后寫入的變量值。
2.有序性:通過對volatile修飾的變量讀寫操作前后加上各種特定的內存屏障來禁止指令重排。
3.原子性:不能保證原子性,面試不要說
8.1 JVM層面的內存屏障和硬件層面的內存屏障
JVM層面有4個內存屏障:寫入STORELOAD,STORESTORE,讀取LOADLOAD,LOADSTORE,但在x86處理器中只有STORELOAD有用,其他三個操作都是空操作。
STORELOAD:在load之前會將store之前的所有寫入操作可見,兼具了其他三種的功能。
硬件層面的內存屏障有4個:lfence-讀屏障,sfence-寫屏障,mfence-讀寫屏障的功能都有,lock-非內存屏障,通過對總線和緩存加鎖,達到內存屏障的功能。
內存屏障的功能:1.阻止屏障兩邊的指令重排序 2.使當前處理器中的緩存立即刷回到主內存。
?
?
原文鏈接:https://blog.csdn.net/qq_21575929/article/details/122567333
- 上一篇:并發編程之CAS和Atomic原子操作類
- 下一篇:如果解決tomcat端口號被占用
相關推薦
- 2022-11-05 Python入門教程之運算符重載詳解_python
- 2022-10-21 IDEA集成Docker實現一鍵部署的詳細過程_docker
- 2022-12-29 Android動態加載布局實現技巧介紹_Android
- 2022-03-14 跨域問題Response to preflight request doesn't pass acc
- 2023-07-15 react實現路由懶加載
- 2023-05-30 Python嵌套循環的使用_python
- 2023-01-13 解決安裝torch后,torch.cuda.is_available()結果為false的問題_py
- 2022-06-16 Kotlin對象比較注意點示例詳解_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支