前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大数据必学Java基础(七十九):线程通信问题

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

原创
作者头像
Lansonli
发布2022-11-08 01:01:21
2040
发布2022-11-08 01:01:21
举报
文章被收录于专栏:Lansonli技术博客

线程通信问题

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

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

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

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

代码结果展示:

代码:

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

2)线程1:生产者

3)线程2:消费者

一、分解1

出现问题:

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

2)打印数据错乱

哈尔滨 - null

费列罗啤酒

哈尔滨巧克力

----没有加同步

代码展示:

代码语言:javascript
复制
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】利用同步代码块解决问题

代码语言:javascript
复制
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】利用同步方法解决问题:

代码语言:javascript
复制
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】代码:

代码语言:javascript
复制
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 返回之前,每个线程都必须重新获取锁。

更改代码:

代码语言:javascript
复制
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();
        }
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ​线程通信问题
    • 一、分解1
      • 二、分解2
        • 三、分解3
          • 四、Loc锁情况下的线程通信
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档