大数据必学Java基础(七十九):线程通信问题

举报
Lansonli 发表于 2022/11/08 01:03:18 2022/11/08
【摘要】 ​线程通信问题应用场景:生产者和消费者问题假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止代码结果展示:;代码:1)商品:属性,品牌 ,名字2)线程1:生产者3)线程...

线程通信问题

应用场景:生产者和消费者问题

假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费

如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止

如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

代码结果展示:

;

代码:

1)商品:属性,品牌 ,名字

2)线程1:生产者

3)线程2:消费者

一、分解1

出现问题:

1)生产者和消费者没有交替输出

2)打印数据错乱

哈尔滨 - null

费列罗啤酒

哈尔滨巧克力

----没有加同步

代码展示:

package com.lanson.test10;

/**
 * @author : Lansonli
 */
public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //setter,getter方法;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


package com.lanson.test10;

/**
 * @author : Lansonli
 */
public class ProducerThread extends Thread{//生产者线程
    //共享商品:
    private Product p;

    public ProducerThread(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
            if(i % 2 == 0){
                //生产费列罗巧克力
                p.setBrand("费列罗");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setName("巧克力");
            }else{
                //生产哈尔滨啤酒
                p.setBrand("哈尔滨");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setName("啤酒");
            }

            //将生产信息做一个打印:
            System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
        }
    }
}


package com.lanson.test10;

/**
 * @author : Lansonli
 */
public class CustomerThread extends Thread{//消费者线程
    //共享商品:
    private Product p;

    public CustomerThread(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//i:消费次数
            System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
        }
    }
}


package com.lanson.test10;

/**
 * @author : Lansonli
 */
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //共享的商品:
        Product p = new Product();
        //创建生产者和消费者线程:
        ProducerThread pt = new ProducerThread(p);
        CustomerThread ct = new CustomerThread(p);

        pt.start();
        ct.start();
    }
}


二、分解2

【1】利用同步代码块解决问题

package com.lanson.test10;

/**
 * @author : Lansonli
 */
public class ProducerThread extends Thread{//生产者线程
    //共享商品:
    private Product p;

    public ProducerThread(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
            synchronized (p){
                if(i % 2 == 0){
                    //生产费列罗巧克力
                    p.setBrand("费列罗");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p.setName("巧克力");
                }else{
                    //生产哈尔滨啤酒
                    p.setBrand("哈尔滨");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p.setName("啤酒");
                }

                //将生产信息做一个打印:
                System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
            }
        }
    }
}


package com.lanson.test10;

/**
 * @author : Lansonli
 */
public class CustomerThread extends Thread{//消费者线程
    //共享商品:
    private Product p;

    public CustomerThread(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//i:消费次数
            synchronized (p){
                System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
            }
        }
    }
}


【2】利用同步方法解决问题:

package com.lanson.test11;

/**
 * @author : Lansonli
 */
public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //setter,getter方法;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //生产商品
    public synchronized void setProduct(String brand,String name){
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);


        //将生产信息做一个打印:
        System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
    }

    //消费商品:
    public synchronized void getProduct(){
        System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
    }
}


package com.lanson.test11;

/**
 * @author : Lansonli
 */
public class CustomerThread extends Thread{//消费者线程
    //共享商品:
    private Product p;

    public CustomerThread(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//i:消费次数
            p.getProduct();;
        }
    }
}


public class ProducerThread extends Thread{//生产者线程
    //共享商品:
    private Product p;

    public ProducerThread(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
            if(i % 2 == 0){
                p.setProduct("费列罗","巧克力");
            }else{
                p.setProduct("哈尔滨","啤酒");
            }
        }
    }
}


(这个else中的代码在分解3中 演示了错误)

三、分解3

【1】原理:

【2】代码:

package com.lanson.test11;

/**
 * @author : Lansonli
 */
public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;

    //引入一个灯:true:红色  false 绿色
    boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
    //setter,getter方法;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //生产商品
    public synchronized void setProduct(String brand,String name){
        if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //灯是绿色的,就生产:
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        //将生产信息做一个打印:
        System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());

        //生产完以后,灯变色:变成红色:
        flag = true;
        //告诉消费者赶紧来消费:
        notify();
    }

    //消费商品:
    public synchronized void getProduct(){
        if(!flag){//flag == false没有商品,等待生产者生产:
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //有商品,消费:
        System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());

        //消费完:灯变色:
        flag = false;
        //通知生产者生产:
        notify();
    }
}


【3】原理

;

注意:wait方法和notify方法 是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)

注意:sleep和wait的区别:sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁

【4】线程生命周期完整图

;

四、Loc锁情况下的线程通信

Condition是在Java Java1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。

它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition

一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。

Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  • Conditon中的await()对应Object的wait()
  • Condition中的signal()对应Object的notify()
  • Condition中的signalAll()对应Object的notifyAll()

void await() throws InterruptedException

造成当前线程在接到信号或被中断之前一直处于等待状态。

与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal()方法,并且碰巧将当前线程选为被唤醒的线程;
  • 其他某个线程调用此 Condition 的signalAll() signalAll()方法;
  • 其他某个线程中断当前线程,且支持中断线程的挂起;
  • 发生“虚假唤醒”

在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证它保持此锁。

void signal()

唤醒一个等待线程。

如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

void signalAll()

唤醒所有等待线程。

如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。

更改代码:

package com.lanson.test12;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : Lansonli
 */
public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;

    //声明一个Lock锁:
    Lock lock = new ReentrantLock();
    //搞一个生产者的等待队列:
    Condition produceCondition = lock.newCondition();
    //搞一个消费者的等待队列:
    Condition consumeCondition = lock.newCondition();
    //引入一个灯:true:红色  false 绿色
    boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
    //setter,getter方法;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //生产商品
    public void setProduct(String brand,String name){
        lock.lock();
        try{
            if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
                try {
                    //wait();
                    //生产者阻塞,生产者进入等待队列中
                    produceCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //灯是绿色的,就生产:
            this.setBrand(brand);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setName(name);
            //将生产信息做一个打印:
            System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());

            //生产完以后,灯变色:变成红色:
            flag = true;
            //告诉消费者赶紧来消费:
            //notify();
            consumeCondition.signal();
        }finally {
            lock.unlock();
        }
    }

    //消费商品:
    public void getProduct(){
        lock.lock();
        try{
            if(!flag){//flag == false没有商品,等待生产者生产:
                try {
                   // wait();
                    //消费者等待,消费者线程进入等待队列:
                    consumeCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //有商品,消费:
            System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());

            //消费完:灯变色:
            flag = false;
            //通知生产者生产:
            //notify();
            produceCondition.signal();
        }finally {
            lock.unlock();
        }
    }
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。