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對象