首頁 > 軟體

Java 詳細講解執行緒安全與同步附範例與註釋

2022-04-14 13:03:07

執行緒安全問題

多個執行緒可能會共用(存取)同一個資源

比如存取同一個物件,同一個變數,同一個檔案

當多個執行緒存取同一塊資源時,很容易引發資料錯亂和資料安全問題,稱為執行緒安全問題

什麼情況下會出現執行緒安全問題

多個執行緒共用同一個資源

且至少有一個執行緒正在執行寫的操作

範例:

存錢取錢問題

分別有存錢和取錢2個執行緒

   存錢                      取錢
   執行緒1         餘額         執行緒2
   1000  《----1000------》 1000
   1000+1000-----》2000
                500 《-----1000-500

正確:結束後餘額應該是1500,而不是500

買票問題

有賣票2個執行緒

   賣票                      賣票
   執行緒1         票數         執行緒2
   1000  《----1000------》 1000
   1000-1-----》999
                999 《-----1000-1

正確:結束後餘額應該是998,而不是999

買票問題錯誤(未執行緒同步)範例:

public class love implements Runnable{
    private int piao=3000;//有3000張票
    public boolean sale() {//ture代表還有票;false代表沒有票了
        if(piao<1) return false;
         piao--;//賣1張票
         
         //細化piao--;
         //暫存器=piao;
         //暫存器=暫存器-1;
         //piao=暫存器;
         
         String sk =Thread.currentThread().getName();//獲取當前執行緒(買票視窗)的名字
         System.out.println(sk+"賣了1張票,還剩下"+piao+"張");
         return piao>1;
    }
    public void run() {
         while(sale());//迴圈執行;直至賣完票返回false
    }
}
 
public class Main {
    public static void main(String[] a) {
        love tjlove =new love();
        for(int i=1;i<=4;i++) {//迴圈4次;產生4個執行緒(視窗)賣票
            Thread tj = new Thread(tjlove());
            tj.setName(""+i);
            tj.start();
        }
    }
}

部分輸出結果:

執行緒安全問題

分析問題

執行緒A和B對類中1個變數值為17進行+1操作
最終結果為2個18

解決方案

加鎖:

過程:首先執行緒A先存取到這個17,讀上來後進行加鎖並進去+1的操作改為18
並且17在加鎖期間其它執行緒都不能存取
改完之後再進行寫入,然後再解鎖17
然後再由執行緒B去存取它,再進行加鎖,重複上面操作變成19再解鎖
這樣做能保證在同一時間只有1個執行緒去存取它,這樣就保證了安全;之前錯誤是由於這些執行緒一起去存取了它

執行緒同步

剛剛所說的加鎖操作便是執行緒同步技術

可以使用執行緒同步技術來解決執行緒安全問題

執行緒同步在Java裡有2種做法:

1.同步語句

2.同步方法

同步語句

public class love implements Runnable{
	private int piao=3000;//本人cpu單核效能過強,資料量大些才能看到是4個執行緒在賣票
	public boolean sale() {
		synchronized(this) {//1個執行緒獲取這個物件的鎖,並加鎖;    synchronized作用於整個語句
		//this指向當前物件
		//不能用new Object();這樣會產生新的物件,產生新的鎖
		//把this換成"123",效果基本一樣;因為其存在常數值裡,每次存取的物件一樣
			if(piao<1) return false;
			piao--;
			String sk =Thread.currentThread().getName();
			System.out.println(sk+"賣了1張票,還剩下"+piao+"張");
			return piao>0;
			}
	}
	public void run() {
		 while(sale());
	}
}

部分輸出結果:

synchronize(obj)的原理

1.每個物件都有一個與它相關的內部鎖(intrinsic lock)或者叫監視器鎖(monitor lock)

2.第一個執行到同步語句的執行緒可以獲得 obj 的內部鎖,在執行完同步語句中的程式碼後釋放此鎖

3.只要一個執行緒持有了內部鎖,那麼其它執行緒在同一時刻將無法再獲得此鎖

✓ 當它們試圖獲取此鎖時,將會進入BLOCKED狀態

4.多個執行緒存取同一個 synchronized(obj)語句時

obj必須是同一個物件,才能起到同步的作用

同步方法

public class love implements Runnable{
    private int piao=3000;
    public synchronized boolean sale() { //synchronized作用於整個方法
            if(piao<1) return false;
            piao--;
            String sk =Thread.currentThread().getName();
            System.out.println(sk+"賣了1張票,還剩下"+piao+"張");
            return piao>0;
    }
    public void run() {
         while(sale());
    }
}

synchronized不能修飾構造方法

同步方法的本質

實體方法:synchronized (this)

靜態方法:synchronized (Class物件)

同步語句比同步方法更靈活一點

同步語句可以精確控制需要加鎖的程式碼範圍,減少處於BLOCKED狀態的執行緒,充分利用勞動力

使用了執行緒同步技術後

雖然解決了執行緒安全問題,但是降低了程式的執行效率

因為加了鎖就會有處於等待的執行緒,多了加鎖解鎖的操作

所以在真正有必要的時候,才使用執行緒同步技術

到此這篇關於Java 詳細講解執行緒安全與同步附範例與註釋的文章就介紹到這了,更多相關Java 執行緒安全內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com