問題1 談談你對volatile的理解
1 volatile是Java虛擬機提供的輕量級的同步機制
- 保證可見性
- 不保證原子性
- 禁止指令重排
2 再談談JMM,線程安全性獲得保證
JMM(Java內存模型Java Memory Model,簡稱JMM)本身是一種抽象的概念 並不真實存在,它描述的是一組規則或規范通過規范定制了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式.
JMM關於同步規定:
- 線程解鎖前,必須把共享變量的值刷新回主內存
- 線程加鎖前,必須讀取主內存的最新值到自己的工作內存
- 加鎖解鎖是同一把鎖
由於JVM運行程序的實體是線程,而每個線程創建時JVM都會為其創建一個工作內存(有些地方成為棧空間),工作內存是每個線程的私有數據區域,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行,首先要將變量從主內存拷貝到自己的工作空間,然后對變量進行操作,操作完成再將變量寫回主內存,不能直接操作主內存中的變量,各個線程中的工作內存儲存着主內存中的變量副本拷貝,因此不同的線程無法訪問對方的工作內存,此案成間的通訊(傳值) 必須通過主內存來完成,其簡要訪問過程
2.1 可見性
通過前面對JMM的介紹,我們知道
各個線程對主內存中共享變量的操作都是各個線程各自拷貝到自己的工作內存操作后再寫回主內存中的.
這就可能存在一個線程AAA修改了共享變量X的值還未寫回主內存中時 ,另外一個線程BBB又對內存中的一個共享變量X進行操作,但此時A線程工作內存中的共享比那里X對線程B來說並不不可見.這種工作內存與主內存同步延遲現象就造成了可見性問題.
2.2 原子性
number++在多線程下是非線程安全的,如何不加synchronized解決?
2.3 VolatileDemo代碼演示可見性+原子性代碼
2.4 有序性
計算機在執行程序時,為了提高性能,編譯器和處理器常常會做指令重排,一般分為以下3種
- 單線程環境里面確保程序最終執行結果和代碼順序執行的結果一致.
- 處理器在進行重新排序是必須要考慮指令之間的數據依賴性
- 多線程環境中線程交替執行,由於編譯器優化重排的存在,兩個線程使用的變量能否保持一致性是無法確定的,結果無法預測
重排1
public void mySort(){ int x=11;//語句1 int y=12;//語句2 x=x+5;//語句3 y=x*x;//語句4 } 1234 2134 1324
問題: 請問語句4 可以重排后變成第一條碼?
存在數據的依賴性 沒辦法排到第一個
重排2
int a ,b ,x,y=0;線程1線程2x=a;y=b;b=1;a=2;x=0 y=0
如果編譯器對這段代碼進行執行重排優化后,可能出現下列情況:線程1線程2b=1;a=2;x=a;y=b;x=2 y=1
這也就說明在多線程環境下,由於編譯器優化重排的存在,兩個線程使用的變量能否保持一致是無法確定的。
3 你在哪些地方用到過volatile?
3.1 單例模式DCL代碼
public class SingletonDemo { private static volatile SingletonDemo instance=null; private SingletonDemo(){ System.out.println(Thread.currentThread().getName()+"\t 構造方法"); } /** * 雙重檢測機制 */ public static SingletonDemo getInstance(){ if(instance==null){ synchronized (SingletonDemo.class){ if(instance==null){ instance=new SingletonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 1; i <=10; i++) { new Thread(() ->{SingletonDemo.getInstance();},String.valueOf(i)).start(); } } }
3.2 代理模式volatile分析
DCL(雙端檢鎖) 機制不一定線程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因在於某一個線程在執行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化
instance=new SingletonDem(); 可以分為以下步驟(偽代碼)
memory=allocate();//1.分配對象內存空間
instance(memory);//2.初始化對象
instance=memory;//3.設置instance的指向剛分配的內存地址,此時instance!=null
步驟2和步驟3不存在數據依賴關系.而且無論重排前還是重排后程序執行的結果在單線程中並沒有改變,因此這種重排優化是允許的
memory=allocate();//1.分配對象內存空間
instance=memory;//3.設置instance的指向剛分配的內存地址,此時instance!=null 但對象還沒有初始化完.
instance(memory);//2.初始化對象
但是指令重排只會保證串行語義的執行一致性(單線程) 並不會關心多線程間的語義一致性
所以當一條線程訪問instance不為null時,由於instance實例未必完成初始化,也就造成了線程安全問題.
問題2 CAS你知道嗎
1 比較並交換
2 CAS底層原理?如果知道,談談你對UnSafe的理解
atomicInteger.getAndIncrement();
atomicInteger.getAndIncrement()方法的源代碼:
UnSafe類是什么?
- 1.UnSafe,是CAS的核心類 由於Java 方法無法直接訪問底層 ,需要通過本地(native)方法來訪問,UnSafe相當於一個后面,基於該類可以直接操作特額定的內存數據.UnSafe類在於sun.misc包中,其內部方法操作可以向C的指針一樣直接操作內存,因為Java中CAS操作的助興依賴於UNSafe類的方法.注意UnSafe類中所有的方法都是native修飾的,也就是說UnSafe類中的方法都是直接調用操作底層資源執行響應的任務
- 2.變量ValueOffset,便是該變量在內存中的偏移地址,因為UnSafe就是根據內存偏移地址獲取數據的
- 3.變量value和volatile修飾,保證了多線程之間的可見性.
3 CAS缺點
問題3 原子類AtomicInteger的ABA問題談談?原子更新引用知道嗎
1 ABA問題的產生
2 原子引用
3 時間戳原子引用
問題4 我們知道ArrayList是線程不安全,請編寫一個不安全的案例並給出解決方案
1 解決方案1
2 解決方案2
限制不可以使用vector和Collections工具類
問題5 公平鎖/非公平鎖/可重入鎖/遞歸鎖/自旋鎖談談你的理解?請手寫一個自旋鎖
1 公平鎖和非公平鎖
2 可重入鎖(又名遞歸鎖)
3 自旋鎖
4 獨占鎖(寫)/共享鎖(讀)/互斥鎖
5 讀寫鎖
問題6 CountDownLatch/CyclicBarrier/Semaphore使用過嗎?
1 CountDownLatch
2 CyclicBarrier
3 Semaphore
問題7 阻塞隊列知道嗎?
1 隊列+阻塞隊列
2 為什么用?有什么好處?
3 BlockingQueue的核心方法
4 架構梳理+種類分析
5 用在哪里
問題8 線程池用過嗎?ThreadPoolExecutor談談你的理解?
1 為什么使用線程池,優勢
2 線程池如何使用?
3 線程池幾個重要參數介紹?
4 說說線程池的底層工作原理?
問題9 線程池用過嗎?生產上你是如何設置合理參數
1 線程池的拒絕策略請你談談
2 你在工作中單一的/固定數的/可變你的三種創建線程池的方法,你用哪個多?超級大坑
3 你在工作中是如何創建線程池的,是否自定義過線程池使用
4 合理配置線程池你是如何考慮的?
問題10 死鎖編碼及定位分析
1 是什么
2 代碼
3 解決
問題11 Java里面鎖請談談你的理解,能說多少說多少