執行緒同步及執行緒安全
阿新 • • 發佈:2020-09-10
併發
多個執行緒操作同一個資源
執行緒同步
執行緒同步其實是一種等待機制,多個需要同時操作此物件的執行緒進入這個物件的等待池,形成佇列,等待前邊執行緒使用完畢,下一個執行緒再使用
佇列和鎖
每個物件都有一把鎖,佇列加鎖才能保證執行緒的安全性,在Java中叫執行緒同步
synchronized
鎖機制
當一個執行緒獲取一個物件的鎖,其他執行緒只能等待。
就好像一堆人排隊去試衣間試衣服,進去的人要把門鎖上,這樣才能保證別人進不去,才是安全的。
執行緒同步的問題
- 一個執行緒拿到鎖,其他執行緒只能等待,會降低效能
- 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序導致,引起效能問題。就好像一個噓噓的人等一個拉臭臭的人釋放廁所
不安全的買票
程式碼實現
// 定義一個賣票的車站 public class Station implements Runnable { // 定義一個車票總數 private int count = 10; // 定義一個標誌位記錄是否有票 private boolean flag = true; @Override public void run() { // 只要有票,一直賣 while (flag) { // 模擬延時 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } this.sale(); } } // 車站售票 private void sale() { // 如果票賣沒了,直接返回 if (count <= 0) { // 修改標誌位 flag = false; return; } // 賣掉一張票總數減1 System.out.println(Thread.currentThread().getName() + "搶到了第" + count-- + "張票!"); } }
測試程式碼
// 建立一個北京站
Runnable beijing = new Station();
// 建立三個買票的人去買票
new Thread(beijing, "張三").start();
new Thread(beijing, "李四").start();
new Thread(beijing, "王五").start();
執行結果
張三搶到了第9張票! 王五搶到了第10張票! 李四搶到了第9張票! 張三搶到了第8張票! 王五搶到了第7張票! 李四搶到了第6張票! 張三搶到了第5張票! 李四搶到了第4張票! 王五搶到了第3張票! 張三搶到了第2張票! 王五搶到了第1張票! 李四搶到了第0張票!
從執行結果可以看出執行緒不安全
張三,李四同時搶到了第9張票
李四搶到了第0張票。這是因為李四在買票的時候看到的還有1張票,而其他人這個時候也看到了1張票,兩個人同時操作,結果同時賣出了2張票,導致最後出現了0的情況
不安全的取錢
兩個人取同一個賬戶裡的錢
程式碼實現
// 定義一個賬戶
class Account {
private int balance; // 卡內餘額
private String cardNumber; // 銀行卡號
public Account(int balance, String cardNumber) {
this.balance = balance;
this.cardNumber = cardNumber;
System.out.println("銀行卡" + this.cardNumber + "餘額:" + this.balance + "萬!");
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
}
// 定義一個銀行,模擬取款
class Bank extends Thread {
private Account account; // 賬戶
private int drawMoney; // 取出多少錢
private int nowMoney; // 取到了多少錢
public Bank(Account account, int drawMoney, String name) {
super(name); // 取錢人名字看成執行緒名字
this.account = account; // 從哪個賬戶取錢
this.drawMoney = drawMoney; // 取多少錢
}
// 重寫run()方法
@Override
public void run() {
this.draw();
}
// 取錢
private void draw() {
// 餘額不足不能取錢
if (this.account.getBalance() - drawMoney < 0) {
System.out.println(this.getName() + "取錢失敗,餘額不足!");
return;
}
// 模擬延時
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 賬戶的錢減少
this.account.setBalance(this.account.getBalance() - this.drawMoney);
// 取出的錢增多
this.nowMoney += this.drawMoney;
System.out.println(this.getName() + "取出了" + this.nowMoney + "萬!");
System.out.println("銀行卡" + this.account.getCardNumber() + "餘額:" + this.account.getBalance() + "萬!");
}
}
測試程式碼
// 建立一個賬戶
Account account = new Account(100, "6217***");
// 建立老王和他女朋友
new Bank(account, 50, "老王").start();
new Bank(account, 100, "漂亮").start();
執行結果
銀行卡6217***餘額:100萬!
漂亮取出了100萬!
銀行卡6217***餘額:0萬!
老王取出了50萬!
銀行卡6217***餘額:-50萬!
不安全的集合
程式碼實現
// 建立一個list
List<String> list = new ArrayList<>();
// 建立10000個執行緒往list裡寫資料
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
System.out.println("寫資料中……");
list.add(Thread.currentThread().getName());
}).start();
}
// 模擬延時,等待資料新增完
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 列印list長度
System.out.println("list.size() = " + list.size());
執行結果
寫資料中……
寫資料中……
寫資料中……
寫資料中……
寫資料中……
寫資料中……
list.size() = 9999