首先先介紹三個性質
可見性
可見性代表主記憶體中變數更新,執行緒中可以及時獲得最新的值,
下面例子證明了執行緒中可見性的問題
由于發現多次執行都要到主記憶體中取變數,所以會將變數快取到執行緒的作業記憶體,這樣當其他執行緒更新該變數的時候,該執行緒無法得知,導致該執行緒會無限的運行下去,
public class test1 {
private static int flag = 1;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (flag == 1){
}
},"t1");
t1.start();
Thread.sleep(1000);
flag = 2;
}
}
疑問
當我們在這個死回圈中加入一個synchronized關鍵字的話就會將更新
猜測:synchronized會使更新當前執行緒的作業記憶體
public class test1 { private static int flag = 1; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ while (flag == 1){ synchronized ("1"){ } } },"t1"); t1.start(); Thread.sleep(1000); flag = 2; } }
原子性
即多執行緒中指令執行會出現交錯,導致資料讀取錯誤,
比如i++的操作就可以在位元組碼的層面可以被看成以下操作
9 getstatic #9 <com/zhf/test3/test2.i : I> 獲得i
12 iconst_1 將1壓入運算元堆疊
13 isub 將兩數相減
14 putstatic #9 <com/zhf/test3/test2.i : I> 將i變數存盤
然后在多執行緒的情況下,會出現以下程式出現非0的結果,
public class test2 {
private static int i;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int j = 0; j < 400; j++) {
i++;
}
});
Thread t2 = new Thread(()->{
for (int j = 0; j < 400; j++) {
i--;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
設計模式---兩階段停止使用volatile
@Slf4j
public class test3 {
private Thread monitor;
private volatile boolean flag = false;
public static void main(String[] args) {
test3 test3 = new test3();
test3.monitor = new Thread(()->{
while (true){
Thread thread = Thread.currentThread();
if (test3.flag){
log.debug("正在執行后續");
break;
}
try {
log.debug("執行緒正在執行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
// 當行程在睡眠程序中被Interrupte()打斷此時isInterrupted()為false
// 從而當例外被抓住后會繼續執行
// 所以要呼叫下面方法繼續將isInterrupted()置為true
// thread.interrupt();
}
}
});
test3.start();
try {
Thread.sleep(5500);
} catch (InterruptedException e) {
e.printStackTrace();
}
test3.stop();
}
public void stop(){
flag = true;
monitor.interrupt();
}
public void start(){
monitor.start();
}
}
設計模式---猶豫模式
具體實作,最常見的就是單例模式,
首先是餓漢模式,這里的多執行緒安全是由JVM保證的,物件是在類加載的加載階段創建的,
class SingtonHungry{
private static Object object = new Object();
// 餓漢模式
public synchronized Object getObject() {
return object;
}
}
其次就是餓漢模式,最常見的不過就是下面的進行多執行緒安全的方案,文章后面會對其進行優化,
class SingtonLazy{
private Object object;
// 懶漢模式
// 由于這樣的話不管有沒有創建出物件都要加鎖然后才能取物件,性能太差
public synchronized Object getObject() {
if (object == null){
object = new Object();
return object;
}
return object;
}
}
有序性
JVM會對指令進行重排序,其和CPU的流水線操作類似,當需要流水線操作的時候,需要進行優化的時候,就會對CPU指令進行重排序優化,
當操作的順序變了之后,就會出現問題,可能會導致條件的提前觸發等等,
Volatile使用
使用域: Volatile只能在類的靜態成員變數或者成員變數上,
volatile識別符號能夠讓執行緒強制去讀主存的該變數的值,保證了執行緒變數的可見性,
volatile識別符號能夠讓執行緒去順序執行該變數的操作,保證了執行變數的陳述句的有序性
-
在讀取該變數時,會為其添加讀屏障,在該讀屏障之后的代碼不會放在讀屏障之前執行,
-
在寫該變數時,會為其添加寫屏障,在該寫屏障之前的代碼不會在屏障之后執行,
所以在volatile的修飾下,能夠保證變數的可見性和有序性,但并不能保證其的原子性,
class SingtonLazy{
// 加上volatile的主要目的就是防止在synchronized內的代碼指令重排,正常是先構造好物件然后賦物件地址
// 導致object會被首先賦予了地址,導致其不為null,然而構造方法還沒有開始構造
// 被其他的執行緒拿走會出現使用出錯,
private static volatile Object object;
// 懶漢模式
public static Object getObject() {
if (object != null){
return object;
}else{
// 這里可能出現這里的執行緒還沒有為其進行宣告物件,但已經由執行緒進入了等待鎖
// 所以需要在這里來一個為空判斷,
synchronized (SingtonLazy.class){
// 這里可能會出現指令重排,所以要加上volatile
if(object == null){
object = new Object();
}
return object;
}
}
}
}
實作單例的另外一個方式
public class Singleton {
// 當使用到ObjectHolder才會進行到這個靜態內部類的加載,同時才會創建該類
// 也是屬于懶漢式
private static class ObjectHolder{
static final Singleton singleton = new Singleton();
}
}
synchronized補充
首先在synchronized代碼塊中,它會保證代碼塊中的可見性,原子性和有序性,
有序性僅僅是表現在synchronized的執行后最后的結果都是一樣的,并不會阻止JVM在其內部進行代碼的重排序,就比如上個例子來說,在synchronized代碼塊中最后代碼的執行結果都是一樣的,但可能由于其優化,導致其他執行緒出錯,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/469890.html
標籤:其他
