1、死鎖
當業務比較複雜,多線程應用裏有可能會發生死鎖
- 線程1 首先佔有對象1,接着試圖佔有對象2
- 線程2 首先佔有對象2,接着試圖佔有對象1
- 線程1 等待線程2釋放對象2
- 與此同時,線程2等待線程1釋放對象1
就會。。。一直等待下去,直到天荒地老,海枯石爛,山無棱 ,天地合。。。
package multiplethread;
import charactor.Hero;
public class TestThread {
public static void main(String[] args) {
final Hero ahri = new Hero();
ahri.name = "九尾妖狐";
final Hero annie = new Hero();
annie.name = "安妮";
Thread t1 = new Thread(){
public void run(){
//佔有九尾妖狐
synchronized (ahri) {
System.out.println("t1 已佔有九尾妖狐");
try {
//停頓1000毫秒,另一個線程有足夠的時間佔有安妮
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("t1 試圖佔有安妮");
System.out.println("t1 等待中 。。。。");
synchronized (annie) {
System.out.println("do something");
}
}
}
};
t1.start();
Thread t2 = new Thread(){
public void run(){
//佔有安妮
synchronized (annie) {
System.out.println("t2 已佔有安妮");
try {
//停頓1000毫秒,另一個線程有足夠的時間佔有暫用九尾妖狐
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("t2 試圖佔有九尾妖狐");
System.out.println("t2 等待中 。。。。");
synchronized (ahri) {
System.out.println("do something");
}
}
}
};
t2.start();
}
}
2、JAVA 線程之間的交互 WAIT和NOTIFY
線程之間有交互通知的需求,考慮如下情況:
有兩個線程,處理同一個英雄。
一個加血,一個減血。
減血的線程,發現血量=1,就停止減血,直到加血的線程爲英雄加了血,纔可以繼續減血
3、不好的解決方式
故意設計減血線程頻率更高,蓋倫的血量遲早會到達1
減血線程中使用while循環判斷是否是1,如果是1就不停的循環,直到加血線程回覆了血量
這是不好的解決方式,因爲會大量佔用CPU,拖慢性能
- Hero.java
package charactor;
public class Hero{
public String name;
public float hp;
public int damage;
public synchronized void recover(){
hp=hp+1;
}
public synchronized void hurt(){
hp=hp-1;
}
public void attackHero(Hero h) {
h.hp-=damage;
System.out.format("%s 正在攻擊 %s, %s的血變成了 %.0f%n",name,h.name,h.name,h.hp);
if(h.isDead())
System.out.println(h.name +"死了!");
}
public boolean isDead() {
return 0>=hp?true:false;
}
}
- TestThread.java
package multiplethread;
import java.awt.GradientPaint;
import charactor.Hero;
public class TestThread {
public static void main(String[] args) {
final Hero gareen = new Hero();
gareen.name = "蓋倫";
gareen.hp = 616;
Thread t1 = new Thread(){
public void run(){
while(true){
//因爲減血更快,所以蓋倫的血量遲早會到達1
//使用while循環判斷是否是1,如果是1就不停的循環
//直到加血線程恢復了血量
while(gareen.hp==1){
continue;
}
gareen.hurt();
System.out.printf("t1 爲%s 減血1點,減少血後,%s的血量是%.0f%n",gareen.name,gareen.name,gareen.hp);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t1.start();
Thread t2 = new Thread(){
public void run(){
while(true){
gareen.recover();
System.out.printf("t2 爲%s 回血1點,增加血後,%s的血量是%.0f%n",gareen.name,gareen.name,gareen.hp);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t2.start();
}
}
4、使用wait和notify進行線程交互
在Hero類中:hurt()減血方法:當hp=1的時候,執行this.wait()。
this.wait(): 表示 讓佔有this的線程等待,並臨時釋放佔有
進入hurt方法的線程必然是減血線程,this.wait()會讓減血線程臨時釋放對this的佔有。 這樣加血線程,就有機會進入recover()加血方法了。
recover() 加血方法:增加了血量,執行this.notify();
this.notify() 表示通知那些等待在this的線程,可以甦醒過來了。 等待在this的線程,恰恰就是減血線程。 一旦recover()結束, 加血線程釋放了this,減血線程,就可以重新佔有this,並執行後面的減血工作。
- Hero.java
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public synchronized void recover() {
hp = hp + 1;
System.out.printf("%s 回血1點,增加血後,%s的血量是%.0f%n", name, name, hp);
// 通知那些等待在this對象上的線程,可以醒過來了,如第20行,等待着的減血線程,甦醒過來
this.notify();
}
public synchronized void hurt() {
if (hp == 1) {
try {
// 讓佔有this的減血線程,暫時釋放對this的佔有,並等待
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
hp = hp - 1;
System.out.printf("%s 減血1點,減少血後,%s的血量是%.0f%n", name, name, hp);
}
public void attackHero(Hero h) {
h.hp -= damage;
System.out.format("%s 正在攻擊 %s, %s的血變成了 %.0f%n", name, h.name, h.name, h.hp);
if (h.isDead())
System.out.println(h.name + "死了!");
}
public boolean isDead() {
return 0 >= hp ? true : false;
}
}
- TestThread.java
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public synchronized void recover() {
hp = hp + 1;
System.out.printf("%s 回血1點,增加血後,%s的血量是%.0f%n", name, name, hp);
// 通知那些等待在this對象上的線程,可以醒過來了,如第20行,等待着的減血線程,甦醒過來
this.notify();
}
public synchronized void hurt() {
if (hp == 1) {
try {
// 讓佔有this的減血線程,暫時釋放對this的佔有,並等待
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
hp = hp - 1;
System.out.printf("%s 減血1點,減少血後,%s的血量是%.0f%n", name, name, hp);
}
public void attackHero(Hero h) {
h.hp -= damage;
System.out.format("%s 正在攻擊 %s, %s的血變成了 %.0f%n", name, h.name, h.name, h.hp);
if (h.isDead())
System.out.println(h.name + "死了!");
}
public boolean isDead() {
return 0 >= hp ? true : false;
}
}
5、關於wait、notify和notifyAll
留意wait()和notify() 這兩個方法是什麼對象上的?
public synchronized void hurt() {
。。。
this.wait();
。。。
}
public synchronized void recover() {
。。。
this.notify();
}
這裏需要強調的是,wait方法和notify方法,並不是Thread線程上的方法,它們是Object上的方法。
因爲所有的Object都可以被用來作爲同步對象,所以準確的講,wait和notify是同步對象上的方法。
wait()的意思是: 讓佔用了這個同步對象的線程,臨時釋放當前的佔用,並且等待。 所以調用wait是有前提條件的,一定是在synchronized塊裏,否則就會出錯。
notify() 的意思是,通知一個等待在這個同步對象上的線程,你可以甦醒過來了,有機會重新佔用當前對象了。
notifyAll() 的意思是,通知所有的等待在這個同步對象上的線程,你們可以甦醒過來了,有機會重新佔用當前對象了。