歡迎關注公眾號“程式設計師那些破事兒”,獲取更多視訊教程
1、什麼是執行緒安全,為什麼會有安全問題
當多個執行緒同時共享,同一個全域性變數或靜態變數,做寫的操作時,可能會發生資料衝突問題,也就是執行緒安全問題。但是做讀操作是不會發生資料衝突問題。
案例:需求現在有100張火車票,有兩個視窗同時搶火車票,請使用多執行緒模擬搶票效果。
class ThreadTrain implements Runnable { private int count = 100; private static Object oj = new Object(); @Override public void run() { while (count > 0) { try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } sale(); } } private void sale() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票"); count--; } } } public class TreadDemo { public static void main(String[] args) { ThreadTrain threadTrain = new ThreadTrain(); Thread t1 = new Thread(threadTrain, "1號視窗"); Thread t2 = new Thread(threadTrain, "2號視窗"); t1.start(); t2.start(); } }
執行結果如下:出現重複售票了。 多執行緒共享一個全域性變數時,做寫操作可能會發生資料衝突問題
2、執行緒安全解決辦法
就上面存在的問題,我們該如何解決呢?如何解決多執行緒之間執行緒安全問題?
使用多執行緒之間同步synchronized(自動上鎖和解鎖)或使用鎖lock(手動)。 將可能會發生資料衝突問題(執行緒不安全問題),只能讓當前一個執行緒進行執行。程式碼執行完成後釋放鎖,讓後才能讓其他執行緒進行執行。這樣的話就可以解決執行緒不安全問題。
1、同步程式碼塊
synchronized(同一個資料){ 可能會發生執行緒衝突問題 } 接著上面的案例的程式碼,給sale()方法修改為如下就可以啦!
private void sale() {
synchronized (oj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
count--;
}
}
}
}
2、同步函式
什麼是同步函式? 就是在方法上修飾synchronized稱為同步函式
把sale()方法改寫如下就可以啦!
private synchronized void sale() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票"); count--; } }
結果如下: 思考:同步程式碼塊和同步函式用的是什麼鎖?
同步函式的使用的鎖是this。 同步函式和同步程式碼塊的區別: 同步函式的鎖是固定的this。 同步程式碼塊的鎖是任意的物件。(this,object等)
3、多執行緒會出現死鎖,什麼是多執行緒死鎖?
同步中巢狀同步,導致鎖無法釋放
class ThreadTrain6 implements Runnable {
// 這是貨票總票數,多個執行緒會同時共享資源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
// 鎖(同步程式碼塊)在什麼時候釋放? 程式碼執行完, 自動釋放鎖.
// 如果flag為true 先拿到 obj鎖,在拿到this 鎖、 才能執行。
// 如果flag為false先拿到this,在拿到obj鎖,才能執行。
// 死鎖解決辦法:不要在同步中巢狀同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
}
public class TreadDemo {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個例項
Thread thread1 = new Thread(threadTrain, "一號視窗");
Thread thread2 = new Thread(threadTrain, "二號視窗");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
4、多執行緒的三大特性
原子性
即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
可見性
當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。 若兩個執行緒在不同的cpu,那麼執行緒1改變了i的值還沒重新整理到主存,執行緒2又使用了i,那麼這個i值肯定還是之前的,執行緒1對變數的修改執行緒沒看到這就是可見性問題。
有序性
程式執行的順序按照程式碼的先後順序執行。 一般來說處理器為了提高程式執行效率,可能會對輸入程式碼進行優化,它不保證程式中各個語句的執行先後順序同程式碼中的順序一致,但是它會保證程式最終執行結果和程式碼順序執行的結果是一致的。
5、Java記憶體模型
共享記憶體模型指的就是Java記憶體模型(簡稱JMM),JMM決定一個執行緒對共享變數的寫入時,能對另一個執行緒可見。從抽象的角度來看,JMM定義了執行緒和主記憶體之間的抽象關係:執行緒之間的共享變數儲存在主記憶體(main memory)中,每個執行緒都有一個私有的本地記憶體(local memory),本地記憶體中儲存了該執行緒以讀/寫共享變數的副本。本地記憶體是JMM的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器優化。
6、volatile與synchronized區別
僅靠volatile不能保證執行緒的安全性。(原子性) ①volatile輕量級,只能修飾變數。synchronized重量級,還可修飾方法 ②volatile只能保證資料的可見性,不能用來同步,因為多個執行緒併發訪問volatile修飾的變數不會阻塞。 synchronized不僅保證可見性,而且還保證原子性,因為,只有獲得了鎖的執行緒才能進入臨界區,從而保證臨界區中的所有語句都全部執行。多個執行緒爭搶synchronized鎖物件時,會出現阻塞。 執行緒安全性包括兩個方面,①可見性。②原子性。 僅僅使用volatile並不能保證執行緒安全性。而synchronized則可實現執行緒的安全性。
.
作者:程式設計師那些破事兒 出處:https://blog.csdn.net/wjq6940 歡迎投稿分享個人工作,生活,專案經驗。 號內有各類程式設計教學視訊資源哦!