java 线程交互_Java自学-多线程 交互

Java 线程之间的交互 wait和notify

线程之间有交互通知的需求,考虑如下情况:

有两个线程,处理同一个英雄。

一个加血,一个减血。

减血的线程,发现血量=1,就停止减血,直到加血的线程为英雄加了血,才可以继续减血

步骤 1 : 不好的解决方式

故意设计减血线程频率更高,盖伦的血量迟早会到达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;

}

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;

}

}

.

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();

}

}

步骤 2 : 使用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,并执行后面的减血工作。

e28b334c0d3d94d62814b0c7cab48bef.png

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;

}

}

.

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){

//无需循环判断

// while(gareen.hp==1){

// continue;

// }

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();

}

}

步骤 3 : 关于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() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。

生产者消费者问题是一个非常典型性的线程交互的问题。

使用栈来存放数据

1.1 把栈改造为支持线程安全

1.2 把栈的边界操作进行处理,当栈里的数据是0的时候,访问pull的线程就会等待。 当栈里的数据是200的时候,访问push的线程就会等待

提供一个生产者(Producer)线程类,生产随机大写字符压入到堆栈

提供一个消费者(Consumer)线程类,从堆栈中弹出字符并打印到控制台

提供一个测试类,使两个生产者和三个消费者线程同时运行,结果类似如下 :

794d390c83b05655c1cc5c8af97f7a81.png

答案 :

MyStack.java

package multiplethread;

import java.util.ArrayList;

import java.util.LinkedList;

public class MyStack {

LinkedList values = new LinkedList();

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();

}

}

ProducerThread.java

package multiplethread;

public class ProducerThread extends Thread{

private MyStack stack;

public ProducerThread(MyStack 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');

}

}

ConsumerThread.java

package multiplethread;

public class ConsumerThread extends Thread{

private MyStack stack;

public ConsumerThread(MyStack 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();

}

}

}

public char randomChar(){

return (char) (Math.random()*('Z'+1-'A') + 'A');

}

}

TestThread.java

package multiplethread;

public class TestThread {

public static void main(String[] args) {

MyStack 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();

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值