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 = ;
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() * + ));
try {
sleep();
} 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() * + ));
try {
sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
程序输出:
支取金额: ,余额为 不足,等待存入!
存入线程交易金额: ,交易后账户余额:
支取线程交易金额: ,交易后账户余额:
存入线程交易金额: ,交易后账户余额:
支取线程交易金额: ,交易后账户余额:
支取金额: ,余额为 不足,等待存入!
存入线程交易金额: ,交易后账户余额:
支取线程交易金额: ,交易后账户余额:
支取线程交易金额: ,交易后账户余额:
存入线程交易金额: ,交易后账户余额:
存入线程交易金额: ,交易后账户余额:
支取线程交易金额: ,交易后账户余额:
存入线程交易金额: ,交易后账户余额:
支取线程交易金额: ,交易后账户余额:
Process finished with exit code (interrupted by signal : SIGINT)
上例中需要了解以下方法:
1. wait():一个线程执行了wait()方法,那么它就会处于等待状态,并进入一个以“锁对象”为标志的线程队列,处于等待状态的线程必须要通过其他线程调用notify/notifyAll方法才可以被唤醒
2. notify():唤醒一个处于等待状态的线程,通常先等待的线程先被唤醒
3. notifyAll():唤醒所有处于等待状态的线程
- wait()、notify()、notifyAll()必须要在同步函数或者同步代码块中调用
- wait()、notify()、notifyAll()的调用者应该是“锁对象”,上例中是account对象