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

Posted Lansonli

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大数据必学Java基础(七十九):线程通信问题相关的知识,希望对你有一定的参考价值。

文章目录

线程通信问题

一、分解1

二、分解2

三、分解3

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


线程通信问题

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

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

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

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

代码结果展示:

 

代码:

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


  • 📢博客主页:https://lansonli.blog.csdn.net
  • 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
  • 📢本文由 Lansonli 原创,首发于 CSDN博客🙉
  • 📢停下休息的时候不要忘了别人还在奔跑,希望大家抓紧时间学习,全力奔赴更美好的生活✨

以上是关于大数据必学Java基础(七十九):线程通信问题的主要内容,如果未能解决你的问题,请参考以下文章

大数据必学Java基础(七十七):线程的生命周期和常见方法

大数据必学Java基础(七十五):多线程与程序进程线程之间概念详解

大数据必学Java基础(七十六):创建线程的三种方式

大数据必学Java基础(七十四):对象流ObjectInputStream和ObjectOutputStream介绍

大数据必学Java基础(七十二):System类对IO流的支持

大数据必学Java基础(七十):不要用字符流去操作非文本文件