1. 程式人生 > 實用技巧 >執行緒同步及執行緒安全

執行緒同步及執行緒安全

併發

多個執行緒操作同一個資源

執行緒同步

執行緒同步其實是一種等待機制,多個需要同時操作此物件的執行緒進入這個物件的等待池,形成佇列,等待前邊執行緒使用完畢,下一個執行緒再使用

佇列和鎖

每個物件都有一把鎖,佇列加鎖才能保證執行緒的安全性,在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