(七)、Java 多線程——死鎖、線程間通信

1、死鎖

當兩個線程相互等待對方釋放同步代碼塊中的“鎖對象”時就會發生死鎖,Java虛擬機沒有監測也沒有採取措施來處理死鎖情況,所以多線程編程中應該採取措施避免死鎖出現。一旦出現死鎖,整個程序既不會拋錯也不會給出任何提示,只是所有線程處於阻塞狀態,無法繼續。
以打開空調爲例:假設打開空調需要遙控器和電池兩個資源

public class OpenAirCondition extends Thread {

    public OpenAirCondition(String name) {
        super(name);
    }

    @Override
    public void run() {
        if("小明".equals(Thread.currentThread().getName())){
            synchronized ("遙控器"){
                System.out.println("小明有遙控器,獲得電池後就可以打開空調...");
                synchronized ("電池"){
                    System.out.println("小明獲得了電池,打開空調");
                }
            }
        }else if("小白".equals(Thread.currentThread().getName())){
            //Thread.sleep(100);
            synchronized ("電池"){
                System.out.println("小白有電池,獲得遙控器後就可以打開空調...");
                synchronized ("遙控器"){
                    System.out.println("小白獲得了遙控器,打開空調");
                }
            }
        }
    }

    public static void main(String[] args){
        OpenAirCondition xiaoming = new OpenAirCondition("小明");
        OpenAirCondition xiaobai = new OpenAirCondition("小白");
        xiaoming.start();
        xiaobai.start();
    }
}

上例中小明有遙控器,獲得電池就可以打開空調;小白有電池,獲得遙控器就可以打開空調。但是“他們彼此並不樂意先分享自己的資源給對方”,導致兩個線程都在等待對方線程的“鎖對象”釋放。這就會導致死鎖,兩個線程都處於阻塞狀態,程序無法繼續。
線程死鎖的輸出
但是程序的輸出並不總是這樣,假如線程“小明”優先獲得了CPU的資源(同時獲得遙控器和電池),使用完畢後釋放這兩個“鎖對象”,那麼小明和小白都可以打開空調。
沒有死鎖發生時的輸出

2、線程間通信

多線程中常見的“生產者-消費者”模式:消費者消費容器中的產品,如果容器中沒有產品,那麼消費者進入等待狀態直到有產品爲止;生產者往容器中添加產品,並通知所有消費者來消費。

編寫一個演示線程間通信的程序:創建並啓動兩個線程,一個線程從賬戶中支取,如果賬戶餘額不足,那麼就處於等待狀態直到餘額足夠支取;另一個線程進行存入,存入後通知支取線程進行取款。

package com.xiaopeng.threadnotify;

class Account {
    //賬戶實時餘額
    private int balance = 0;

    public int getBalance() {
        return balance;
    }

    //支取
    public synchronized void draw(int tranAmt) {

        try {
            while (balance < tranAmt) {
                System.out.println("支取金額:" + tranAmt + " ,餘額爲 " + balance + " 不足,等待存入!");
                wait();    //賬戶餘額不足,等待
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        balance -= tranAmt;

        System.out.println(Thread.currentThread().getName() + "交易金額: " + tranAmt + " ,交易後賬戶餘額: " + balance);

    }

    //存入
    public synchronized void deposit(int tranAmt) {
        balance += tranAmt;
        System.out.println(Thread.currentThread().getName() + "交易金額: " + tranAmt + " ,交易後賬戶餘額: " + balance);
        //存入後喚醒正在等待的線程
        notifyAll();
    }

}

public class ThreadCooperation {

    private Account account = new Account();
    private DrawThread drawThread = new DrawThread("支取線程");
    private DepositThread depositThread = new DepositThread("存入線程");

    public ThreadCooperation() {
        //啓動線程
        this.drawThread.start();
        this.depositThread.start();
    }

    public static void main(String[] args) {
        ThreadCooperation cooperation = new ThreadCooperation();
    }

    //支取線程
    class DrawThread extends Thread {

        public DrawThread(String name) {
            super(name);
        }

        @Override
        public void run() {

            while (true) {
                account.draw((int) (Math.random() * 10 + 1));
                try {
                    sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //存入線程
    class DepositThread extends Thread {

        public DepositThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (true) {
                account.deposit((int) (Math.random() * 10 + 1));
                try {
                    sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

程序輸出:

支取金額:3 ,餘額爲 0 不足,等待存入!
存入線程交易金額: 4 ,交易後賬戶餘額: 4
支取線程交易金額: 3 ,交易後賬戶餘額: 1
存入線程交易金額: 8 ,交易後賬戶餘額: 9
支取線程交易金額: 6 ,交易後賬戶餘額: 3
支取金額:6 ,餘額爲 3 不足,等待存入!
存入線程交易金額: 7 ,交易後賬戶餘額: 10
支取線程交易金額: 6 ,交易後賬戶餘額: 4
支取線程交易金額: 1 ,交易後賬戶餘額: 3
存入線程交易金額: 4 ,交易後賬戶餘額: 7
存入線程交易金額: 9 ,交易後賬戶餘額: 16
支取線程交易金額: 9 ,交易後賬戶餘額: 7
存入線程交易金額: 2 ,交易後賬戶餘額: 9
支取線程交易金額: 3 ,交易後賬戶餘額: 6

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

上例中需要了解以下方法:
1. wait():一個線程執行了wait()方法,那麼它就會處於等待狀態,並進入一個以“鎖對象”爲標誌的線程隊列,處於等待狀態的線程必須要通過其他線程調用notify/notifyAll方法纔可以被喚醒
2. notify():喚醒一個處於等待狀態的線程,通常先等待的線程先被喚醒
3. notifyAll():喚醒所有處於等待狀態的線程


  • wait()、notify()、notifyAll()必須要在同步函數或者同步代碼塊中調用
  • wait()、notify()、notifyAll()的調用者應該是“鎖對象”,上例中是account對象

3、使用BlockingQueue控制線程通信

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章