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,并执行后面的减血工作。

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 把栈改造为支持线程安全
    1.2 把栈的边界操作进行处理,当栈里的数据是0的时候,访问pull的线程就会等待。 当栈里的数据是200的时候,访问push的线程就会等待
  2. 提供一个生产者(Producer)线程类,生产随机大写字符压入到堆栈
  3. 提供一个消费者(Consumer)线程类,从堆栈中弹出字符并打印到控制台
  4. 提供一个测试类,使两个生产者和三个消费者线程同时运行,结果类似如下 :

    答案 :

MyStack.java

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

ProducerThread.java

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');
    }

}

ConsumerThread.java

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

    }

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

    }

}

原文地址:https://www.cnblogs.com/jeddzd/p/12388362.html

时间: 2024-08-05 06:24:39

Java自学-多线程 交互的相关文章

Java自学-多线程 死锁

Java 演示多线程死锁 当业务比较复杂,多线程应用里有可能会发生死锁 步骤 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

Java自学-多线程 原子访问

多线程 原子访问 步骤 1 : 原子性操作概念 所谓的原子性操作即不可中断的操作,比如赋值操作 int i = 5; 原子性操作本身是线程安全的 但是 i++ 这个行为,事实上是有3个原子性操作组成的. 步骤 1. 取 i 的值 步骤 2. i + 1 步骤 3. 把新的值赋予i 这三个步骤,每一步都是一个原子操作,但是合在一起,就不是原子操作.就不是线程安全的. 换句话说,一个线程在步骤1 取i 的值结束后,还没有来得及进行步骤2,另一个线程也可以取 i的值了. 这也是分析同步问题产生的原因

Java学习-多线程交互

1-生产者消费者问题 1. 使用栈来存放数据 1.1 把栈改造为支持线程安全 1.2 把栈的边界操作进行处理,当栈里的数据是0的时候,访问pull的线程就会等待. 当栈里的数据是200的时候,访问push的线程就会等待2. 提供一个生产者(Producer)线程类,生产随机大写字符压入到堆栈3. 提供一个消费者(Consumer)线程类,从堆栈中弹出字符并打印到控制台4. 提供一个测试类,使两个生产者和三个消费者线程同时运行 1 package multiplethread; 2 3 impor

Java自学-多线程 线程池

Java 如何开发一个自定义线程池 每一个线程的启动和结束都是比较消耗时间和占用资源的. 如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢. 为了解决这个问题,引入线程池这种设计思想. 线程池的模式很像 生产者消费者模式,消费的对象是一个一个的能够运行的任务 步骤 1 : 线程池设计思路 线程池的思路和生产者消费者模型是很接近的. 准备一个任务容器 一次性启动10个 消费者线程 刚开始任务容器是空的,所以线程都wait在上面. 直到一个外部线程往这个任务容器中扔了

Java之------多线程(从基础到加强及交互线程)

一.基础篇: 1.线程的定义 线程(thread)是操作系统进程中能够独立执行的实体(控制流),是处理器调度和分派的基本单位. 2.线程的属性 并发性,共享性,动态性,结构性 3.线程的状态 4.线程的调度 ★主要是通过实现Runnable接口和继承Thread类来实现线程的调度和操作 a.Runnable接口(里面就一个run方法,只要通过重写run方法就可以实现自己想要的线程功能) [java] view plain copy public interface Runnable { publ

Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它.比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools, * initialized to indicate thread 'not yet started' */   p

【转】JAVA自学之路

JAVA自学之路 一: 学会选择 为了就业,不少同学参加各种各样的培训. 决心做软件的,大多数人选的是java,或是.net,也有一些选择了手机.嵌入式.游戏.3G.测试等. 那么究竟应该选择什么方向呢? 我的意见是,不要太过相信各种培训机构或是抢手文章的说法(包括我),当你要走向社会的时候,就不要再把自己当成学生,不要把自己的将来交给别人,学会运用自己的眼睛去观察,去了解这个世界吧. 每个培训机构都会宣传自己的好处,并不能说明大的趋势. 一些新闻文章很有可能是枪手写的,不必太过相信.国外背景的

java之 ------ 多线程(从基础到加强)

首先了解线程的一些基本知识: 1.线程的定义: 是操作系统进程中能够独立执行的实体(控制流),是处理器调度和分派的基本单位. 2.线程的属性: 并发性.共享性.动态性和结构性 3.线程的状态 然后就是java与线程的调度: 1.主要是通过实现Runnable接口和继承Thread类来实现线程的调度和操作 a.Runnable接口(里面就一个run方法,只要通过重写run方法就可以实现自己想要的线程功能) public interface Runnable { public abstract vo

马士兵:JAVA自学之路

JAVA自学之路 一:学会选择 为了就业,不少同学参加各种各样的培训.决心做软件的,大多数人选的是java,或是.net,也有一些选择了手机.嵌入式.游戏.3G.测试等. 那么究竟应该选择什么方向呢?我的意见是,不要太过相信各种培训机构或是抢手文章的说法(包括我),当你要走向社会的时候,就不要再把自己当成学生,不要把自己的将来交给别人,学会运用自己的眼睛去观察,去了解这个世界吧. 每个培训机构都会宣传自己的好处,并不能说明大的趋势.一些新闻文章很有可能是枪手写的,不必太过相信.国外背景的教师和课