(七)、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控制线程通信

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