多线程入门04 —— 浅谈线程通信
发布日期:2022-04-11 08:52:52 浏览次数:17 分类:博客文章

本文共 6219 字,大约阅读时间需要 20 分钟。

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

  • 假设仓库中只能存放一件产品 , 生产者将生产出来的产品放入仓库 , 消费者将仓库中产品取走消费
  • 如果仓库中没有产品 , 则生产者将产品放入仓库 , 否则停止生产并等待 , 直到仓库中的产品被消费者取走为止
  • 如果仓库中放有产品 , 则消费者可以将产品取走消费 , 否则停止消费并等待,直到仓库中再次放入产品为止

2. 问题分析

这是一个线程同步问题 , 生产者和消费者共享同一个资源 , 并且生产者和消费者之间相互依赖 , 互为条件。

  • 对于生产者 , 没有生产产品之前 , 要通知消费者等待 . 而生产了产品之后 , 又需要马上通知消费者消费
  • 对于消费者 , 在消费之后 , 要通知生产者已经结束消费 , 需要生产新的产品以供消费.
  • 在生产者消费者问题中 , 仅有synchronized是不够的
  • synchronized 可阻止并发更新同一个共享资源 , 实现了同步
  • synchronized 不能用来实现不同线程之间的消息传递 (通信)

3. Java中涉及到线程通信的方法

方法 作用
wait() 线程会处于等待状态,直到其他线程通知,这个方法会释放锁
wait(long timeout) 指定等待的毫秒
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程 , 优先级别高的线程优先调度
  • 以上方法均定义在Object类当中,只能在同步方法或同步代码块中使用

4. 管程法

  • 生产者 : 负责生产数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;
  • 消费者 : 负责处理数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;
  • 缓冲区 : 消费者不能直接使用生产者的数据 , 他们之间有个缓冲区
  • 处理逻辑:生产者生产好的数据之间放入缓冲区中,消费者从缓冲区中拿出数据
  • 代码示例
    • 思路分析:生产者只管生产,消费者只管消费
      • 缓冲区在添加数据时判断缓冲区是否满了。满了,等待消费;没有满,就通知生产。
      • 缓冲区减少数据时,判断还有没有数据。没有的话。等待生产;消费完毕了,就要通知生产

(1)Productor.java——生产者

package com.pbx.lesson03;/** * 生产者 * @author BruceXu * @date 2020/9/17 */public class Productor extends Thread {    private Buffer buffer;    public Productor(Buffer buffer){        this.buffer = buffer;    }    @Override    public void run() {        for (int i = 0; i < 100; i++) {            System.out.println("生产了第"+i+"只鸡");            buffer.push(new Chicken(i));            // 可以通过修改sleep的时间来模拟不同的生产速度            try {                if (i<10) {                    Thread.sleep(100);                } else {                    Thread.sleep(200);                }            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

(2)Customer.java——消费者

package com.pbx.lesson03;/** * 消费者 * @author BruceXu * @date 2020/9/17 */public class Customer extends Thread {    private Buffer buffer;    public Customer(Buffer buffer) {        this.buffer = buffer;    }    @Override    public void run() {        for (int i = 0; i < 100; i++) {            Chicken chicken = buffer.pop();            System.out.println("消费了第"+chicken.getId()+"只鸡,现在还有"+buffer.getCount()+"只鸡剩余");            try {                // 可以通过修改sleep的时间来模拟不同的消费                Thread.sleep(150);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

(3)Buffer.java——缓冲区

package com.pbx.lesson03;/** * 缓冲区 * @author BruceXu * @date 2020/9/17 */public class Buffer {    // 需要一个容器来存放商品    private Chicken[] chickens = new Chicken[10];    // 需要一个计数器    private int count = 0;    public int getCount() {        return count;    }    // 生产者要放入商品    public synchronized void push(Chicken chicken) {        // 如果容器满了,生产者等待,通知消费者消费        if(count >= chickens.length) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 如果没满,就可以继续放入商品        chickens[count] = chicken;        count++;        //  通知消费者消费        this.notifyAll();    }    // 消费者要消费商品    public synchronized Chicken pop(){        // 判断能不能消费        if (count==0) {            // 等待生产,等待消费            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 消费        count--;        this.notifyAll();        return chickens[count];    }}

(4)Chicken.java——商品

package com.pbx.lesson03;/** * 商品 * * @author BruceXu * @date 2020/9/17 */public class Chicken {    private int id;    public int getId() {        return id;    }    public Chicken(int id) {        this.id = id;    }}

(5)Main.java

package com.pbx.lesson03;/** * @author BruceXu * @date 2020/9/17 */public class Main {    public static void main(String[] args) {        Buffer buffer = new Buffer();        new Productor(buffer).start();        new Customer(buffer).start();    }}

5. 信号灯法

  • 将某个变量作为标志位,以此来进行判断,是否该等待还是通知
  • 代码示例

(1)TV.java

package com.pbx.lesson04;/** * 看电视 * 节目时间广告等待播放,广告时间节目等待播放 * * @author BruceXu * @date 2020/9/18 */public class TV {    // 广告时间,false;节目时间,ture    private boolean flag;    public synchronized void playShow(String name) {        // 播放节目之前,先看广告有没有播放过        if (!flag) {            // 没放过广告,那么等待广告播放            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println("播放节目:" + name);        // 更改标志位        this.flag = !this.flag;        // 节目放过了,该放广告了        this.notifyAll();    }    public synchronized void playAD(String name) {        // 节目放完了吗?如果没有,就等待节目放完        if (flag) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println("播放广告:" + name);        // 更改标志位        this.flag = !this.flag;        // 广告放过了,该放节目了        this.notifyAll();    }}

(2)AD.java

package com.pbx.lesson04;/** * @author BruceXu * @date 2020/9/18 */public class AD extends Thread {    private final TV tv;    public AD(TV tv) {        this.tv = tv;    }    @Override    public void run() {        for (int i = 0; i < 20; i++) {            tv.playAD("第" + i + "个广告,今年过节不收礼,收礼只收脑白金");        }    }}

(3)Show.java

package com.pbx.lesson04;/** * @author BruceXu * @date 2020/9/18 */public class Show extends Thread {    private final TV tv;    public Show(TV tv) {        this.tv = tv;    }    @Override    public void run() {        for (int i = 0; i < 20; i++) {            tv.playShow("第" + i + "个节目,NBA全明星赛");        }    }}

(4)Main.java

package com.pbx.lesson04;/** * @author BruceXu * @date 2020/9/18 */public class Main {    public static void main(String[] args) {        TV tv = new TV();        new Show(tv).start();        new AD(tv).start();    }}

6. 线程池法 (了解为主)

  • 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 解决方法:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 优点
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理(....)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止
  • 相关API:ExecutorService和Executors
    • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
      • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
      • Future
        submit(Callable
        task):执行任务,有返回值,一般用来执行Callable
      • void shutdown() :关闭连接池
    • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

转载地址:https://www.cnblogs.com/primabrucexu/p/13694077.html 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程八 线程池
下一篇:多线程入门03 —— 浅谈线程同步问题

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年02月29日 15时35分54秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

case是不是php语言关键字,PHP语言 switch 的一个注意点 2019-04-21
linux php mkdir失败,linux – mkdir错误:参数无效 2019-04-21
config.php渗透,phpMyAdmin 渗透利用总结 2019-04-21
java list 合并 重复的数据_Java ArrayList合并并删除重复数据3种方法 2019-04-21
android volley 上传图片 和参数,android - 使用android中的volley将图像上传到multipart中的服务器 - 堆栈内存溢出... 2019-04-21
android开发的取消清空按钮,Android开发实现带清空按钮的EditText示例 2019-04-21
android gp服务,ArcGIS Runtime SDK for Android开发之调用GP服务(异步调用) 2019-04-21
mysql整体会滚_滚mysql 2019-04-21
向mysql数据库中添加批量数据类型_使用JDBC在MySQL数据库中快速批量插入数据 2019-04-21
最全的mysql 5.7.13_最全的mysql 5.7.13 安装配置方法图文教程(linux) 强烈推荐! 2019-04-21
mssql连接mysql数据库文件_在本地 怎么远程连接MSSQL数据库 2019-04-21
mssql 远程无法连接mysql_解决SQLServer远程连接失败的问题 2019-04-21
linux mysql c++编程_Linux下进行MYSQL的C++编程起步手记 2019-04-21
Maria数据库怎么复制到mysql_MySQL、MariaDB数据库的AB复制配置过程 2019-04-21
mysql5.6 icp mrr bak_【mysql】关于ICP、MRR、BKA等特性 2019-04-21
mysql utf8跟utf8mb4_MySQL utf8 和 utf8mb4 的区别 2019-04-21
docker mysql开机自启动_Docker学习4-学会如何让容器开机自启服务【坑】 2019-04-21
在mysql中删除表正确的是什么_在MySQL中删除表的操作教程 2019-04-21
mysql有3个共同好友_共同好友mysql 2019-04-21
代理查询 mysql_查询数据库代理设置 2019-04-21