一、CAS:(Compare and swap)
作用:
在沒有鎖的狀態下,保證多個線程對同一個值的更新
實現:
用戶態一直循環直到修改成功
do{}while(修改成功);
ABA問題:(你和女朋友分手,之後她和別的男的在一起,之後又和你複合了)
更新值的過程中,其他線程修改了。
解決方法:
-1.給這個值添加一個版本號,每次被修改時增加版本號,當你修改時判斷版本號是否和你當初讀取的一樣
-2.給這個值添加一個Boolean類型,標記是否被修改過。
本質是如何實現的(與synchronized,volatile底層實現一樣)
jdk中unsafe調用native方法。
底層有直接的指令支持CAS:lock cmpxchg(lock表示,當執行cmpxchg指令時,不能被打斷)
二、JOL(java object layout)
//pem.xml配置文件修改
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
//打印對象佈局
public class ObjectTest {
public static void main(String[] args) {
Object o = new Object();
//查看Object對象佈局
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
對象在內存中的內存佈局
字段 | 大小 |
---|---|
markword(對象頭) | 8字節 |
class pointer(類型指針) | 4字節 |
instance data(實例數據) | 如果對象爲空,則爲0 |
padding(填充符) | 4字節(補充爲8的整數倍) |
java查看默認命令行參數
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=132883072 -XX:MaxHeapSize=2126129152 -XX:+PrintCommandLineFl
ags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesInd
ividualAllocation -XX:+UseParallelGC
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
UseCompressedClassPointers: 會將8字節指針壓縮爲4字節(爲什麼8字節,因爲操作系統64位)
下面對象內存大小是多少?
計算大小時,要查看是否開啓指針壓縮(UseCompressedClassPointers)。
Object o = new Object();
o引用:4字節;object對象16字節;總共20字節
markword(對象頭) 8字節(鎖標誌(3),分代年齡(4bit),hashcode(31bit),unusedGC標記)
class pointer(類型指針) 4字節
instance data(實例數據) 如果對象爲空,則爲0
padding(填充符) 4字節(補充爲8的整數倍)
synchronized鎖升級
synchronized上鎖會優化。
對象new(無鎖) -》偏向鎖(默認打開,偏於第一個使用的線程) -》輕量級鎖(自旋鎖,無鎖) -》重量級鎖
這些變化記錄在markword。
查看對象頭
public class ObjectTest {
public static void main(String[] args) {
Object o = new Object();
//查看Object對象佈局
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){ //鎖定對象
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
沒加鎖之前
加鎖之後
鎖的信息保存在對象頭中
過程:
當一個線程對無鎖對象上鎖時,直接將自己的線程指針寫入對象頭(偏向鎖)。
當存在線程競爭鎖時,撤銷偏向鎖,線程內部生成lockrecord對象,並將這
個對象指針貼到對象頭中,並將hashcode保存到lockrecord(自旋鎖,CAS操作)。
解決自旋鎖等待問題,一定情況下(自旋時間長,JVM自己控制),升級爲重量
級鎖(將指向互斥量的指針寫入對象頭)。
鎖降級:
GC時候會出現,沒有意思(爲啥?因爲對象都要刪除了)。
鎖消除(lock eliminate):
public void add(String str1, String str2){
/**
*StringBuilder與StringBuffer區別:
*StringBuffer線程安全,append方法是同步的
*/
StringBuffer str = new StringBuffer();
str.append(str1).append(str2); //str對象不是共享的資源,jvm會消除鎖
}
鎖粗化(lock coarsening):
public String test(String str){
int i = 0;
StringBuffer buffer = new StringBuffer();
while(i < 10){
buffer.append(str); //會將這裏的鎖添加到while之外
i++;
}
return buffer.toString();
}
JIT(just in time compiler,即時編譯)
synchronized實現過程
1.java 代碼: synchronized
2.JVM指令: monitorenter monitorexit
3.執行過程中自動升級
4.機器指令: lock cmpxchg
三、超線程
一個ALU對應的多個PC寄存器的組合。當cpu執行線程2時,ALU直接去另一些寄存器中執行,不需要加載線程2的環境到線程1使用的寄存器中。
四、緩存
cpu有L1,L2緩存,如果多核,那麼多個核共享L3。
五、cache line(64字節)
cpu讀取內存的時候按塊讀取。
緩存行對齊,加速程序執行速度
沒有考慮cache line
public class Cache1 {
private static class T{
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static{
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
考慮cache line
public class Cache2 {
private static class Padding{
public volatile long a1,a2,a3,a4,a5,a6,a7; //讓數據不在同一個cache line中
}
private static class T extends Padding{
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static{
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
六、volatile
作用:
- 保證變量在線程間的可見性
- 禁止指令重排序(防止指令半初始化)
創建對象時,jvm執行三步。1:堆上分配對象(並設爲默認值),2:調用構造函數初始化,3:將初始化好的對象和棧上的引用對應起來。如果不禁止重排序,會發生2,3重排序導致錯誤。
底層如何實現數據一致性:
- (MESI(Modified,Exclusive,Shared,Invalid) Cache一致性協議) Inter CPU,其他也有對應的。(一個cache line中一個數據改變,通知其他數據)
- MESI不行,鎖總線
系統底層如何保證有序性
- 內存屏障(屏障兩邊的指令不可以重排序)
- 鎖總線
volatile如何解決指令重排序
1.代碼中的volatile
2.字節碼,加ACC_VOLATILE標誌
3.JVM加內存屏障
4.hotspot直接把總線鎖了(方便移植)。
Java屏障規則:
讀讀,讀寫,寫讀,寫寫之間可以加屏障
JVM實現細節:
寫操作之前和之後加屏障,讀操作之前和之後加屏障
七、強軟,弱虛引用:
強:棧上的對象引用和堆中內存對象對應,當棧上對象爲空時,堆中對象被回收
Object ob = new Object();
ob = null;
軟:和強引用類似,但是堆內存對象中又包含一個對象,他們之間是弱引用。
作用:
常常用於緩存中。
SoftReference<byte[]> m = new SoftReference<>(new byte[100102410]);
m.get(); //獲取對象地
System.gc(); //沒有釋放回收
如何釋放:當堆內存空間不足時,會自動釋放。
弱:類似軟引用,但是即使引用在也可以直接回收。一次性使用
作用:
防止內存泄漏。Thread Local使用這個技術。
WeakReference<byte[]> m = new WeakReference<>(new byte[100102410]);
System.gc(); //釋放回收
虛:
作用:
管理堆外內存,JVM使用堆外內存(zero copy)
當某個對象關聯到堆外內存的時候,這個對象被回收時,堆外內存也要被回收。
PhantomReference<byte[]> m = new PhantomReference<>(new byte[100102410],QUEUE);
m.get(); //獲取對象地,但是返回null