java進階(38)--執行緒安全

Mrwhite86發表於2021-04-02

文件目錄:

一、概念

二、解決方案

三、舉例說明

---------------------------------------分割線:正文--------------------------------------------------------

一、概念

關注資料在多執行緒併發時安全問題,共享資料有修改的行為。

二、解決方案

1、執行緒排隊執行,不能併發,即執行緒同步機制。

2、使用synchronized(){}執行緒同步程式碼塊,()內填寫需要同步的共享物件

3、區域性變數永遠不存線上程安全問題,因為區域性變數是不會共享的

三、舉例說明

1、編寫程式模擬兩個執行緒同時對同一個賬戶進行去取款操作

(1)變成賬戶類

 1 package JAVAADVANCE;
 2 
 3 public class Account {
 4     //賬戶與餘額
 5     private String actNo;
 6     private double balance;
 7 
 8     public Account(String actNo, double balance) {
 9         this.actNo = actNo;
10         this.balance = balance;
11     }
12 
13     public String getActNo() {
14         return actNo;
15     }
16 
17     public void setActNo(String actNo) {
18         this.actNo = actNo;
19     }
20 
21     public double getBalance() {
22         return balance;
23     }
24 
25     public void setBalance(double balance) {
26         this.balance = balance;
27     }
28     //取款
29     public void withdraw(double money){
30         //取款之前的餘額
31         double before=this.getBalance();
32         //取款之後的餘額
33         double after = before-money;
34         //更新餘額
35         this.setBalance(after);
36     }
37 
38 }

(2)編寫賬戶執行緒類

 1 package JAVAADVANCE;
 2 
 3 public class AccountThread extends Thread {
 4     //兩個執行緒必須共享同一個賬戶物件
 5     private Account act;
 6     //通過構造方法傳遞過來的賬戶物件
 7     public AccountThread(Account act){
 8         this.act=act;
 9     }
10     @Override
11     public void run() {
12         //run方法執行表示取款操作
13         //假設取款5000
14         double money=5000;
15         //取款
16         act.withdraw(money);
17         System.out.println(Thread.currentThread().getName()+"對賬戶"+act.getActNo()+"取款成功,餘額為:"+act.getBalance());
18     }
19 }

(3)測試類進行取款操作

 1 package JAVAADVANCE;
 2 
 3 public class TestAdvance38TestThreadSafe01 {
 4     public static void main(String[] args) {
 5         //建立賬戶物件,只建立一個
 6         Account act=new Account("act-001",10000);
 7         //建立兩個執行緒
 8         Thread t1=new AccountThread(act);
 9         Thread t2=new AccountThread(act);
10         //設定name
11         t1.setName("t1");
12         t2.setName("t2");
13         //啟動執行緒取款
14         t1.start();
15         t2.start();
16     }
17 
18 }

(4)檢視執行結果,一定機率出現錢被取完,餘額未更新,出現執行緒安全問題

t2對賬戶act-001取款成功,餘額為:5000.0
t1對賬戶act-001取款成功,餘額為:5000.0

2、改進程式碼,使用執行緒同步機制來解決以上問題

 (1)加入synchronized ()執行緒同步程式碼塊

()內需要填寫多執行緒共享的資料,才能達到多執行緒排隊。

 1     //取款
 2     public void withdraw(double money){
 3         //增加執行緒同步機制,裡面是執行緒同步程式碼塊
 4         synchronized (this){
 5             //取款之前的餘額
 6             double before=this.getBalance();
 7             //取款之後的餘額
 8             double after = before-money;
 9             try {
10                 Thread.sleep(1000);
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14             //更新餘額
15             this.setBalance(after);
16         }
17 
18     }

改進後再次執行,多執行緒不會併發

t1對賬戶act-001取款成功,餘額為:5000.0
t2對賬戶act-001取款成功,餘額為:0.0

(2)whithdraw呼叫時候增加,synchronized ()執行緒同步程式碼塊,擴大了同步範圍,執行效率變低

 1     @Override
 2     public void run() {
 3         //run方法執行表示取款操作
 4         //假設取款5000
 5         double money=5000;
 6         //取款
 7         synchronized (act){
 8         act.withdraw(money);}
 9         System.out.println(Thread.currentThread().getName()+"對賬戶"+act.getActNo()+"取款成功,餘額為:"+act.getBalance());
10     }

執行效果同上:多執行緒不會併發

(3)將例項方法使用synchronized

缺點:一定需要鎖this,整個方法體都需要同步,可能會擴大同步範圍導致程式效率過低

有點:程式碼比較少,簡介

 1     //取款
 2     public synchronized void withdraw(double money){
 3         //增加執行緒同步機制,裡面是執行緒同步程式碼塊
 4 //        synchronized (this){
 5             //取款之前的餘額
 6             double before=this.getBalance();
 7             //取款之後的餘額
 8             double after = before-money;
 9             try {
10                 Thread.sleep(1000);
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14             //更新餘額
15             this.setBalance(after);
16         }

 

相關文章