線程之間有交互通知的需求,考慮如下情況:
有兩個線程,處理同一個英雄。
一個加血,一個減血。
減血的線程,發現血量=1,就停止減血,直到加血的線程爲英雄加了血,纔可以繼續減血
不使用wait和notify的解決方式
故意設計減血線程頻率更高,蓋倫的血量遲早會到達1
減血線程中使用while循環判斷是否是1,如果是1就不停的循環,直到加血線程回覆了血量
這是不好的解決方式,因爲會大量佔用CPU,拖慢性能
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;
}
}
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){
System.out.println("蓋倫的hp:" + gareen.hp);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
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();
}
}
使用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,並執行後面的減血工作。
package charactor;
public class Hero {
public String name;
public float hp;
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);
}
}
package multiplethread;
import java.awt.GradientPaint;
import charactor.Hero;
public class TestThread7{
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){
gareen.hurt();
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();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t2.start();
}
}
關於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() 的意思是,通知所有的等待在這個同步對象上的線程,你們可以甦醒過來了,有機會重新佔用當前對象了。
練習-生產者消費者問題
生產者消費者問題是一個非常典型性的線程交互的問題。
- 使用棧來存放數據
- 把棧改造爲支持線程安全
- 把棧的邊界操作進行處理,當棧裏的數據是0的時候,訪問pull的線程就會等待。 當棧裏的數據是200的時候,訪問push的線程就會等待
- 提供一個生產者(Producer)線程類,生產隨機大寫字符壓入到堆棧
- 提供一個消費者(Consumer)線程類,從堆棧中彈出字符並打印到控制檯
- 提供一個測試類,使兩個生產者和三個消費者線程同時運行,結果類似如下 :
線程安全的棧
package multiplethread;
import java.util.ArrayList;
import java.util.LinkedList;
public class MyStack<T> {
LinkedList<T> values = new LinkedList<T>();
public synchronized void push(T t) {
while(values.size()>=200){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notifyAll();
values.addLast(t);
}
public synchronized T pull() {
while(values.isEmpty()){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notifyAll();
return values.removeLast();
}
public T peek() {
return values.getLast();
}
}
生產者線程
package multiplethread;
public class ProducerThread extends Thread{
private MyStack<Character> stack;
public ProducerThread(MyStack<Character> stack,String name){
super(name);
this.stack =stack;
}
public void run(){
while(true){
char c = randomChar();
System.out.println(this.getName()+" 壓入: " + c);
stack.push(c);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public char randomChar(){
return (char) (Math.random()*('Z'+1-'A') + 'A');
}
}
消費者線程
package multiplethread;
public class ConsumerThread extends Thread{
private MyStack<Character> stack;
public ConsumerThread(MyStack<Character> stack,String name){
super(name);
this.stack =stack;
}
public void run(){
while(true){
char c = stack.pull();
System.out.println(this.getName()+" 彈出: " + c);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
測試
package multiplethread;
public class TestThread {
public static void main(String[] args) {
MyStack<Character> stack = new MyStack<>();
new ProducerThread(stack, "Producer1").start();
new ProducerThread(stack, "Producer2").start();
new ConsumerThread(stack, "Consumer1").start();
new ConsumerThread(stack, "Consumer2").start();
new ConsumerThread(stack, "Consumer3").start();
}
}