1. JVM與線程安全
可見性:當多個線程對一個線程進行操作的時候,其中一個線程修改了變量的值,而其他的線程並不知道該值已經被修改
可見性-synchronized
JMM關於synchronized的兩條規定:
1、線程解鎖前,必須把共享變量的最新值刷新到主內存
2、線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值(注意:加鎖與解鎖是同一把鎖)
可見性-volatile
通過加入內存屏障和禁止重排序優化來實現
1、對volatile變量寫操作時,會在寫操作後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存
2、對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存讀取共享變量
原子性:修改操作不可被切割
2. synchronized關鍵字原理
線程安全概念:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,
就是線程安全的。
線程安全問題都是又全局變量以及靜態變量引起的。
如果每個線程對全局變量、靜態變量只有讀操作,沒有寫操作,一般來說是線程安全的。如果多個線程同時執行寫操作,一般需要考慮線程同步,否則的話可能會影響線程安全。
synchronized作用:加鎖,所有synchronize方法都會順序執行
執行方式:
- 嘗試獲取鎖
- 如果獲得了鎖,執行synchronized的方法體的內容
- 如果無法獲得鎖,則不斷的嘗試獲得鎖。一旦鎖被釋放,則多個線程會同時嘗試獲得鎖,造成鎖競爭的問題。【鎖競爭在高併發、線程數量高是會引發CPU佔用居高不下,甚至直接宕機】
3. 對象鎖和類鎖
synchronized作用在非靜態方法上代表的是對象鎖,一個對象一個鎖,多個對象之間不會發生鎖競爭
synchronized作用在靜態方法時,則升級爲類鎖,所有該類的對象共享一把鎖,存在鎖競爭。
4. 對象鎖的同步和異步
同一個對象中所有的synchronized方法都是同步執行的。非synchronized方法異步執行。
synchronized加鎖的最小粒度是對象。
例如:如果一個對象中有兩個synchronized方法func1和func2,兩個線程分別對應func1和func2,這兩個方法之間也會存在鎖競爭。
5. 髒讀問題
多個線程訪問同一個資源,在一個線程修改數據的過程中,有另外一個線程來讀取數據,就會引起髒讀。
爲了避免髒讀,在操作時要保證數據修改操作的原子性,並且對讀操作也要進行同步控制
6.鎖重入
同一個線程得到了一個對象鎖之後,再次請求此對象是可以再次獲取該對象的鎖。
同一個對象的多個synchronized方法可以重入
synchronized A()在調用synchronized B()方法時, A與B不會存在鎖競爭,而是存在鎖重入。
父子類的鎖可以重入
7. 拋出異常釋放鎖
一個線程在獲得鎖之後執行操作,如果發生錯誤拋出異常,則走自動釋放鎖。
- 可以利用拋出異常,主動釋放鎖
- 程序異常時,防止資源被死鎖,無法釋放
- 異常釋放鎖可能導致數據不一致
8. synchronized代碼塊
相同類型的鎖互斥,不同類型的鎖互不干擾。
如果在線程內修改了鎖的引用,則會導致鎖失效。
修改鎖對象的屬性不會導致鎖失效,修改鎖對象的引用會導致鎖失效。
final關鍵字的含義?
final在Java中是一個保留的關鍵字,可以聲明成員變量、方法、類以及本地變量。一旦你將引用聲明作final,你將不能改變這個引用了,編譯器會檢查代碼,如果你試圖將變量再次初始化的話,編譯器會報編譯錯誤。
修改鎖引用導致鎖失效的DEMO
package com.jimmy.test;
/**
* 修改鎖的引用導致鎖失效
*/
public class Test {
private String lock = "object lock";
private void method() {
synchronized (lock) {
try {
System.out.println("start - " + Thread.currentThread().getName() + " use " + lock);
// 如果在這裏修改了鎖的引用,則鎖會失效
lock = "changed object lock";
Thread.sleep(2000);
System.out.println("end - " + Thread.currentThread().getName() + " use " + lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Test test = new Test();
Thread t1 = new Thread(() -> test.method(), "t1");
Thread t2 = new Thread(() -> test.method(), "t2");
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
鎖不失效的結果:
鎖失效的結果:
在聲明鎖的時候使用final關鍵字,可以避免鎖被修改
9. 死鎖
死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。
10. 線程之間的通信
object類中的wait/notify 可以實現線程通信
wait/notify必須要synchronized關鍵字一起使用
wait 釋放鎖
notify 不釋放鎖,只發出通知
實例:
TODO