一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - Java 多線程實例詳解(三)

Java 多線程實例詳解(三)

2020-06-11 15:42Corn JAVA教程

本文主要介紹 java 線程安全的知識,這里整理了相關資料及實現示例代碼,有興趣的小伙伴可以參考下

本文主要接著前面多線程的兩篇文章總結Java多線程中的線程安全問題。

一.一個典型的Java線程安全例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class ThreadTest {
 
 public static void main(String[] args) {
  Account account = new Account("123456", 1000);
  DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable(account, 700);
  Thread myThread1 = new Thread(drawMoneyRunnable);
  Thread myThread2 = new Thread(drawMoneyRunnable);
  myThread1.start();
  myThread2.start();
 }
 
}
 
class DrawMoneyRunnable implements Runnable {
 
 private Account account;
 private double drawAmount;
 
 public DrawMoneyRunnable(Account account, double drawAmount) {
  super();
  this.account = account;
  this.drawAmount = drawAmount;
 }
 
 public void run() {
  if (account.getBalance() >= drawAmount) { //1
   System.out.println("取錢成功, 取出錢數為:" + drawAmount);
   double balance = account.getBalance() - drawAmount;
   account.setBalance(balance);
   System.out.println("余額為:" + balance);
  }
 }
}
 
class Account {
 
 private String accountNo;
 private double balance;
 
 public Account() {
 
 }
 
 public Account(String accountNo, double balance) {
  this.accountNo = accountNo;
  this.balance = balance;
 }
 
 public String getAccountNo() {
  return accountNo;
 }
 
 public void setAccountNo(String accountNo) {
  this.accountNo = accountNo;
 }
 
 public double getBalance() {
  return balance;
 }
 
 public void setBalance(double balance) {
  this.balance = balance;
 }
 
}

上面例子很容易理解,有一張銀行卡,里面有1000的余額,程序模擬你和你老婆同時在取款機進行取錢操作的場景。多次運行此程序,可能具有多個不同組合的輸出結果。其中一種可能的輸出為:

1 取錢成功, 取出錢數為:700.0
2 余額為:300.0
3 取錢成功, 取出錢數為:700.0
4 余額為:-400.0

也就是說,對于一張只有1000余額的銀行卡,你們一共可以取出1400,這顯然是有問題的。

經過分析,問題在于Java多線程環境下的執行的不確定性。CPU可能隨機的在多個處于就緒狀態中的線程中進行切換,因此,很有可能出現如下情況:當thread1執行到//1處代碼時,判斷條件為true,此時CPU切換到thread2,執行//1處代碼,發現依然為真,然后執行完thread2,接著切換到thread1,接著執行完畢。此時,就會出現上述結果。

因此,講到線程安全問題,其實是指多線程環境下對共享資源的訪問可能會引起此共享資源的不一致性。因此,為避免線程安全問題,應該避免多線程環境下對此共享資源的并發訪問。

二.同步方法

對共享資源進行訪問的方法定義中加上synchronized關鍵字修飾,使得此方法稱為同步方法。可以簡單理解成對此方法進行了加鎖,其鎖對象為當前方法所在的對象自身。多線程環境下,當執行此方法時,首先都要獲得此同步鎖(且同時最多只有一個線程能夠獲得),只有當線程執行完此同步方法后,才會釋放鎖對象,其他的線程才有可能獲取此同步鎖,以此類推...

在上例中,共享資源為account對象,當使用同步方法時,可以解決線程安全問題。只需在run()方法前加上synshronized關鍵字即可。

?
1
2
3
4
5
public synchronized void run() {
  
 // ....
 
}

三.同步代碼塊

正如上面所分析的那樣,解決線程安全問題其實只需限制對共享資源訪問的不確定性即可。使用同步方法時,使得整個方法體都成為了同步執行狀態,會使得可能出現同步范圍過大的情況,于是,針對需要同步的代碼可以直接另一種同步方式——同步代碼塊來解決。

同步代碼塊的格式為:

?
1
2
3
4
5
synchronized (obj) {
   
 //...
 
}

其中,obj為鎖對象,因此,選擇哪一個對象作為鎖是至關重要的。一般情況下,都是選擇此共享資源對象作為鎖對象。

如上例中,最好選用account對象作為鎖對象。(當然,選用this也是可以的,那是因為創建線程使用了runnable方式,如果是直接繼承Thread方式創建的線程,使用this對象作為同步鎖會其實沒有起到任何作用,因為是不同的對象了。因此,選擇同步鎖時需要格外小心...)

四.Lock對象同步鎖

上面我們可以看出,正因為對同步鎖對象的選擇需要如此小心,有沒有什么簡單點的解決方案呢?以方便同步鎖對象與共享資源解耦,同時又能很好的解決線程安全問題。

使用Lock對象同步鎖可以方便的解決此問題,唯一需要注意的一點是Lock對象需要與資源對象同樣具有一對一的關系。Lock對象同步鎖一般格式為:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class X {
 
 // 顯示定義Lock同步鎖對象,此對象與共享資源具有一對一關系
 private final Lock lock = new ReentrantLock();
 
 public void m(){
  // 加鎖
  lock.lock();
  
  //... 需要進行線程安全同步的代碼
  
  // 釋放Lock鎖
  lock.unlock();
 }
 
}

 五.wait()/notify()/notifyAll()線程通信

在博文《Java總結篇系列:java.lang.Object》中有提及到這三個方法,雖然這三個方法主要都是用于多線程中,但實際上都是Object類中的本地方法。因此,理論上,任何Object對象都可以作為這三個方法的主調,在實際的多線程編程中,只有同步鎖對象調這三個方法,才能完成對多線程間的線程通信。

wait():導致當前線程等待并使其進入到等待阻塞狀態。直到其他線程調用該同步鎖對象的notify()或notifyAll()方法來喚醒此線程。

notify():喚醒在此同步鎖對象上等待的單個線程,如果有多個線程都在此同步鎖對象上等待,則會任意選擇其中某個線程進行喚醒操作,只有當前線程放棄對同步鎖對象的鎖定,才可能執行被喚醒的線程。

notifyAll():喚醒在此同步鎖對象上等待的所有線程,只有當前線程放棄對同步鎖對象的鎖定,才可能執行被喚醒的線程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.qqyumidi;
 
public class ThreadTest {
 
 public static void main(String[] args) {
  Account account = new Account("123456", 0);
 
  Thread drawMoneyThread = new DrawMoneyThread("取錢線程", account, 700);
  Thread depositeMoneyThread = new DepositeMoneyThread("存錢線程", account, 700);
 
  drawMoneyThread.start();
  depositeMoneyThread.start();
 }
 
}
 
class DrawMoneyThread extends Thread {
 
 private Account account;
 private double amount;
 
 public DrawMoneyThread(String threadName, Account account, double amount) {
  super(threadName);
  this.account = account;
  this.amount = amount;
 }
 
 public void run() {
  for (int i = 0; i < 100; i++) {
   account.draw(amount, i);
  }
 }
}
 
class DepositeMoneyThread extends Thread {
 
 private Account account;
 private double amount;
 
 public DepositeMoneyThread(String threadName, Account account, double amount) {
  super(threadName);
  this.account = account;
  this.amount = amount;
 }
 
 public void run() {
  for (int i = 0; i < 100; i++) {
   account.deposite(amount, i);
  }
 }
}
 
class Account {
 
 private String accountNo;
 private double balance;
 // 標識賬戶中是否已有存款
 private boolean flag = false;
 
 public Account() {
 
 }
 
 public Account(String accountNo, double balance) {
  this.accountNo = accountNo;
  this.balance = balance;
 }
 
 public String getAccountNo() {
  return accountNo;
 }
 
 public void setAccountNo(String accountNo) {
  this.accountNo = accountNo;
 }
 
 public double getBalance() {
  return balance;
 }
 
 public void setBalance(double balance) {
  this.balance = balance;
 }
 
 /**
  * 存錢
  *
  * @param depositeAmount
  */
 public synchronized void deposite(double depositeAmount, int i) {
 
  if (flag) {
   // 賬戶中已有人存錢進去,此時當前線程需要等待阻塞
   try {
    System.out.println(Thread.currentThread().getName() + " 開始要執行wait操作" + " -- i=" + i);
    wait();
    // 1
    System.out.println(Thread.currentThread().getName() + " 執行了wait操作" + " -- i=" + i);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  } else {
   // 開始存錢
   System.out.println(Thread.currentThread().getName() + " 存款:" + depositeAmount + " -- i=" + i);
   setBalance(balance + depositeAmount);
   flag = true;
 
   // 喚醒其他線程
   notifyAll();
 
   // 2
   try {
    Thread.sleep(3000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println(Thread.currentThread().getName() + "-- 存錢 -- 執行完畢" + " -- i=" + i);
  }
 }
 
 /**
  * 取錢
  *
  * @param drawAmount
  */
 public synchronized void draw(double drawAmount, int i) {
  if (!flag) {
   // 賬戶中還沒人存錢進去,此時當前線程需要等待阻塞
   try {
    System.out.println(Thread.currentThread().getName() + " 開始要執行wait操作" + " 執行了wait操作" + " -- i=" + i);
    wait();
    System.out.println(Thread.currentThread().getName() + " 執行了wait操作" + " 執行了wait操作" + " -- i=" + i);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  } else {
   // 開始取錢
   System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount + " -- i=" + i);
   setBalance(getBalance() - drawAmount);
 
   flag = false;
 
   // 喚醒其他線程
   notifyAll();
 
   System.out.println(Thread.currentThread().getName() + "-- 取錢 -- 執行完畢" + " -- i=" + i); // 3
  }
 }
 
}

上面的例子演示了wait()/notify()/notifyAll()的用法。部分輸出結果為:

取錢線程 開始要執行wait操作 執行了wait操作 -- i=0
存錢線程 存款:700.0 -- i=0
存錢線程-- 存錢 -- 執行完畢 -- i=0
存錢線程 開始要執行wait操作 -- i=1
取錢線程 執行了wait操作 執行了wait操作 -- i=0
取錢線程 取錢:700.0 -- i=1
取錢線程-- 取錢 -- 執行完畢 -- i=1
取錢線程 開始要執行wait操作 執行了wait操作 -- i=2
存錢線程 執行了wait操作 -- i=1
存錢線程 存款:700.0 -- i=2
存錢線程-- 存錢 -- 執行完畢 -- i=2
取錢線程 執行了wait操作 執行了wait操作 -- i=2
取錢線程 取錢:700.0 -- i=3
取錢線程-- 取錢 -- 執行完畢 -- i=3
取錢線程 開始要執行wait操作 執行了wait操作 -- i=4
存錢線程 存款:700.0 -- i=3
存錢線程-- 存錢 -- 執行完畢 -- i=3
存錢線程 開始要執行wait操作 -- i=4
取錢線程 執行了wait操作 執行了wait操作 -- i=4
取錢線程 取錢:700.0 -- i=5
取錢線程-- 取錢 -- 執行完畢 -- i=5
取錢線程 開始要執行wait操作 執行了wait操作 -- i=6
存錢線程 執行了wait操作 -- i=4
存錢線程 存款:700.0 -- i=5
存錢線程-- 存錢 -- 執行完畢 -- i=5
存錢線程 開始要執行wait操作 -- i=6
取錢線程 執行了wait操作 執行了wait操作 -- i=6
取錢線程 取錢:700.0 -- i=7
取錢線程-- 取錢 -- 執行完畢 -- i=7
取錢線程 開始要執行wait操作 執行了wait操作 -- i=8
存錢線程 執行了wait操作 -- i=6
存錢線程 存款:700.0 -- i=7

由此,我們需要注意如下幾點:

1.wait()方法執行后,當前線程立即進入到等待阻塞狀態,其后面的代碼不會執行;

2.notify()/notifyAll()方法執行后,將喚醒此同步鎖對象上的(任意一個-notify()/所有-notifyAll())線程對象,但是,此時還并沒有釋放同步鎖對象,也就是說,如果notify()/notifyAll()后面還有代碼,還會繼續進行,知道當前線程執行完畢才會釋放同步鎖對象;

3.notify()/notifyAll()執行后,如果右面有sleep()方法,則會使當前線程進入到阻塞狀態,但是同步對象鎖沒有釋放,依然自己保留,那么一定時候后還是會繼續執行此線程,接下來同2;

4.wait()/notify()/nitifyAll()完成線程間的通信或協作都是基于不同對象鎖的,因此,如果是不同的同步對象鎖將失去意義,同時,同步對象鎖最好是與共享資源對象保持一一對應關系;

5.當wait線程喚醒后并執行時,是接著上次執行到的wait()方法代碼后面繼續往下執行的。

當然,上面的例子相對來說比較簡單,只是為了簡單示例wait()/notify()/noitifyAll()方法的用法,但其本質上說,已經是一個簡單的生產者-消費者模式了。

 系列文章:

java 多線程實例講解 (一)
Java 多線程實例詳解(二)
Java 多線程實例詳解(三)

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 成在线人免费视频一区二区三区 | 极品美女aⅴ高清在线观看 极品ts赵恩静和直男激战啪啪 | 国产精品第一区揄拍 | 亚洲国产精品久久丫 | 和肥岳在厨房激情 | 日韩一级精品视频在线观看 | www.色小妹| 欧美在线一 | 午夜十八岁禁 | 亚欧洲乱码视频一二三区 | 日韩在线二区 | 国产精品亚欧美一区二区三区 | 日韩一区二区三区四区区区 | 精品国产美女AV久久久久 | 亚洲国产精久久久久久久 | 国产不卡视频一区二区在线观看 | 亚洲成色爱我久久 | 福利视频一区二区牛牛 | 免费观看毛片视频 | juliaann大战黑人 | 99视频九九精品视频在线观看 | 91人人在线| 东北恋哥在线播放免费播放 | 午夜在线观看免费完整直播网 | 私人影院在线免费观看 | 隔壁老王国产精品福利 | 夫妇野外交换激情 | 福利视频一区二区思瑞 | 日韩精选视频 | 欧洲美女女同 | 国产老村长足疗店对白 | 午夜欧美精品 | 亚洲高清毛片一区二区 | 成人免费草草视频 | 99精品国产高清自在线看超 | 娇妻被健身教练挺进小说阅读 | 午夜国产精品视频在线 | 国产亚洲精品自在线亚洲情侣 | 91制片厂制作果冻传媒123 | 欧美四区| 久久一er精这里有精品 |