多執行緒安全問題詳解
阿新 • • 發佈:2019-01-06
在上一篇部落格中已經提到了什麼是多執行緒:https://blog.csdn.net/weixin_42647847/article/details/80969240
那多執行緒在java中是如何實現的呢。
一、實現多執行緒的四種方式
1.繼承Thread類,重寫run方法2.實現Runnable介面,實現run方法,實現Runnable介面的實現類的例項物件作為Thread建構函式的target3.通過Callable和FutureTask建立執行緒4.通過執行緒池的方式建立執行緒。程式碼例項:各實現方式的比較:通過Thread和Runable的方式無法獲取到執行緒執行的反回結果,因為Runable中的run方法反回的是void,而通過使用Callable和FutureTask建立執行緒,則可以獲取到執行緒的執行結果,因為Callabel介面中的call方法返回一個物件,而在FutureTask中也提供了相應對執行緒操作的方法public class ThreadDemo01 extends Thread{ public ThreadDemo01(){ //編寫子類的構造方法,可預設 } public void run(){ //編寫自己的執行緒程式碼 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args){ ThreadDemo01 threadDemo01 = new ThreadDemo01(); threadDemo01.setName("我是自定義的執行緒1"); threadDemo01.start(); System.out.println(Thread.currentThread().toString()); } } public class ThreadDemo02 { public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); Thread t1 = new Thread(new MyThread()); t1.start(); } } class MyThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName()+"-->我是通過實現介面的執行緒實現方式!"); } } public class ThreadDemo03 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Callable<Object> oneCallable = new Tickets<Object>(); FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable); Thread t = new Thread(oneTask); System.out.println(Thread.currentThread().getName()); t.start(); } } class Tickets<Object> implements Callable<Object>{ //重寫call方法 @Override public Object call() throws Exception { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName()+"-->我是通過實現Callable介面通過FutureTask包裝器來實現的執行緒"); return null; } } public class ThreadDemo05{ private static int POOL_NUM = 10; //執行緒池數量 /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ExecutorService executorService = Executors.newFixedThreadPool(5); for(int i = 0; i<POOL_NUM; i++) { RunnableThread thread = new RunnableThread(); //Thread.sleep(1000); executorService.execute(thread); } //關閉執行緒池 executorService.shutdown(); } } class RunnableThread implements Runnable { @Override public void run() { System.out.println("通過執行緒池方式建立的執行緒:" + Thread.currentThread().getName() + " "); } }
二、執行緒的6種狀態與轉換
NEW狀態,指執行緒剛剛建立,尚未啟動。Runnable狀態,是執行緒正在正常執行, 可能會有某種耗時計算、IO等待、cpu時間片切換等,這個狀態下可能發生的等待,而不是鎖,sleep等。Blocked這個狀態下,是在多個執行緒有同步操作的場景,比如在等待另一個執行緒的synchronized塊的執行釋放,或者可重入的synchronized塊裡呼叫wait方法,也就是這裡是執行緒在等待進入臨界區。四、多執行緒三個核心概念
原子性即一個操作可能包含很多子操作,要麼全部執行,要麼全部不執行。可見性可見是指當多個執行緒併發訪問共享變數時,一個執行緒對共享變數的修改,其他執行緒能夠立即看到,CPU從主記憶體中讀資料的效率相對來說不高,現在主流的計算機中,都有幾級快取。每個執行緒讀取共享變數時,都會將該變數載入進其對應CPU的快取記憶體裡,修改該變數後,CPU會立即更新該快取,但並不一定會立即將其寫回主記憶體(實際上寫回主記憶體的時間不可預期)。此時其它執行緒(尤其是不在同一個CPU上執行的執行緒)訪問該變數時,從主記憶體中讀到的就是舊的資料,而非第一個執行緒更新後的資料。順序性順序性指的是,程式執行的順序按照程式碼的先後順序執行。五、如何解決多執行緒併發問題
1.保證操作的原子性 a.常見的機制是鎖和程式碼同步,使用鎖,可以保證同一時間只有一個執行緒能拿到鎖,也就保證了同一時間只有一個執行緒能執行鎖之間的程式碼同步方法或者同步程式碼塊。使用非靜態同步方法時,鎖住的是當前例項;使用靜態同步方法時,鎖住的是該類的Class物件;使用靜態程式碼塊時,鎖住的是synchronized關鍵字後面括號內的物件。無論使用鎖還是synchronized,本質都是一樣,通過鎖來實現資源的排它性,從而實際目的碼段同一時間只會被一個執行緒執行,進而保證了目的碼段的原子性。這是一種以犧牲效能為代價的方法。 b.CASJava中提供了對應的原子操作類來實現該操作,並保證原子性,其本質是利用了CPU級別的CAS指令。由於是CPU級別的指令,其開銷比需要作業系統參與的鎖的開銷小。2.保證可見性java提供了volatile關鍵字來保證可見性。當使用volatile修飾某個變數時,它會保證對該變數的修改會立即被更新到記憶體中,並且將其它快取中對該變數的快取設定成無效,因此其它執行緒需要讀取該值時必須從主記憶體中讀取,從而得到最新的值。3.保證順序性通過volatile關鍵字修飾變數和synchronized和鎖來保證順序性。JVM還通過被稱為happens-before原則隱式地保證順序性。兩個操作的執行順序只要可以通過happens-before推匯出來,則JVM會保證其順序性,反之JVM對其順序性不作任何保證,可對其進行任意必要的重新排序以獲取高效率。程式的重排序java程式在執行的過程中,會經過重排序以獲取更高的執行效率,程式經過編譯器和處理器都會對程式指令進行重排序,在單執行緒中對存在控制依賴的操作重排序,不會改變結果,但多執行緒中,可以會改變程式的執行結果,所以在JMM中使用了happens-before原則,來保證操作間的可見性。happens-before原則(先行發生原則)
- 程式順序規則:一個執行緒中的每個操作,happens-before於隨後該執行緒中的任意後續操作
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的獲取
- volatile變數規則:對一個volatile域的寫,happens-before於對這個變數的讀
- 傳遞性:如果A happens-before B,B happens-before C,那麼A happens-before C
- start規則:如果執行緒A執行執行緒B的start方法,那麼執行緒A的ThreadB.start()happens-before於執行緒B的任意操作
- join規則:如果執行緒A執行執行緒B的join方法,那麼執行緒B的任意操作happens-before於執行緒A從TreadB.join()方法成功返回。
五、Synchronized
synchronized關鍵字是程式最常用的保證執行緒安全的手段。它的作用就是確保執行緒互斥的訪問同步程式碼,保證共享變數的可見性,有效解決程式執行重排問題實現原理:synchronized修飾方法和synchronized修飾程式碼塊,可以鎖物件也可以鎖類,底層實現原理就是通過每個物件在記憶體中的分配都會有一個物件頭來儲存鎖物件monitor,並用mark word欄位來鎖物件的資訊,每個物件存在著與一個monitor與之關聯,而synchorized就是通過獲取這個monitor來獲取鎖的,對於程式碼塊是使用monitorenter和monitorexit指令來標識同步的,而同步方法則是通過ACC-SYNCHRONIZED來標識的。