final的安全發布
兩個關鍵字“發布”“安全”
所謂發布通俗一點的理解就是創建一個對象,使這個對象能被當前范圍之外的代碼所使用
比如Object o = new Object();
然后接下來使用對象o
但是對于普通變量的創建,之前分析過,大致分為三個步驟:
1、分配內存空間
2、將o指向分配的內存空間
3、調用構造函數來初始化對象
這三個步驟不是原子的,如果執行到第二步,還沒有進行初始化,此時對象已經不是null了,如果被其他代碼訪問,這將收獲一個錯誤的結果。
或者說對象尚未完全創建就被使用了,其他線程看到的結果可能是不一致的,這就是不安全的發布
根本原因就是JVM創建對象的過程涉及到分配空間、指針設置、數據初始化等步驟,并不是同步的,涉及到主存與緩存、處理器與寄存器等,可見性沒辦法得到保障
所以說,什么是安全發布,簡單理解就是對象的創建能夠保障在被別人使用前,已經完成了數據的構造設置,或者說一個對象在使用時,已經完成了初始化。
不幸的是,Java對此并沒有進行保障,你需要自己進行保障,比如synchronized關鍵字,原子性、排他性就可以做到這一點
不安全的發布實例
怎么保障安全發布?有幾種方法:
一種是剛才提到的鎖機制,通過加鎖可以保障中間狀態不會被讀取
另外還有:
1、借助于volatile或者AtomicReference聲明對象
2、借助于final關鍵字
3、在靜態初始化塊中,進行初始化(JVM會保障)
4、將對象引用保存到一個由鎖保護的域中
5、借助AtomicReference
很顯然,對于鎖機制,那些線程安全的容器比如ConcurrentMap,也是滿足這條的,所以也是安全發布
對于final,當你創建一個對象時,使用final關鍵字能夠使得另一個線程不會訪問到處于“部分創建”的對象
因為:當構造函數退出時,final字段的值保證對訪問構造對象的其他線程可見
如果某個成員是final的,JVM規范做出如下明確的保證:
一旦對象引用對其他線程可見,則其final成員也必須正確的賦值
所以說借助于final,就如同你對對象的創建訪問加鎖了一般,天然的就保障了對象的安全發布。
對于普通的變量,對象的內存空間分配、指針設置、數據初始化,和將這個變量的引用賦值給另一個引用,之間是可能發生重排序的,所以也就導致了其他線程可能讀取到不一致的中間狀態
但是對于final修飾的變量,JVM會保障順序
不會在對final變量的寫操作完成之前,與將變量引用賦值給其他變量之間進行重排序,也就是final變量的設置完成始終會在被讀取之前
final除了不可變的定義之外,還與線程安全發布息息相關
借助于final,可以達到對象安全發布的保障,只需要借助于final,不在需要任何額外的付出,他能夠保障在多線程環境下,總是能夠讀取到正確的初始化的值
所以,如果你不希望變量后續被修改,你應該總是使用final關鍵字
而且,很顯然在某些場景下,final也可以解決一定的安全問題
實例
使用synchronized鎖的時候,作為鎖的對象最好要加上final修飾符,因為可能線程會改變鎖變量持有的具體的對象。
demo如下:
public class Test02 {
static Object lock = new Object();
public static void main(String[] args) {Thread t1 = new Thread(() -> {lock = new Object();synchronized (lock) {for (int i = 0; i < 10; i++) {try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A");}}});Thread t2 = new Thread(() -> {lock = new Object();synchronized (lock) {for (int i = 0; i < 10; i++) {try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("B");}}});t1.start();t2.start();
}
}
但是要是把鎖改成final的。代碼如下:
public class Test02 {static final Object lock = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {
// lock = new Object(); // 編譯出錯,final不能修改synchronized (lock) {for (int i = 0; i < 10; i++) {try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A");}}});Thread t2 = new Thread(() -> {
// lock = new Object();synchronized (lock) {for (int i = 0; i < 10; i++) {try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("B");}}});t1.start();t2.start();}
}
實例參考:https://juejin.cn/post/7104070219806539806
原理參考:https://www.cnblogs.com/noteless/p/10416678.html