
多线程入门04 —— 浅谈线程通信
发布日期:2022-04-11 08:52:52
浏览次数:3
分类:博客文章
本文共 5142 字,大约阅读时间需要 17 分钟。
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:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
转载地址:https://www.cnblogs.com/primabrucexu/p/13694077.html 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
关注你微信了!
[***.11.155.53]2022年05月24日 14时43分53秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
最新文章
摘自windows程序设计第五版
2019-12-29 10:43:36
C++11 std::bind std::function 高级用法 (c++11的新特性)
2019-12-29 10:43:36
设计一个只能在堆上或栈上实例化的类
2019-12-29 10:43:36
设置windows程序客户区大小(个人感觉很好用以后就直接拿来用得了)
2019-12-29 10:43:32
矩阵-DirectX与OpenGL的不同
2019-12-29 10:43:33
多物体碰撞检测策略
2019-12-29 10:43:33
Unity3D与Android相互传递消息
2019-12-29 10:43:33
excel中无法打开超链接或者打开是以word打开的解决办法
2019-12-29 10:43:33
Re-installation failed due to different application signatures.解决方案
2019-12-29 10:43:33
setContentView(R.layout.activity_main) Error解决方法
2019-12-29 10:43:34
简单数组算法分享
2019-12-29 10:43:31
A追B如何计算子弹的飞行轨迹
2019-12-29 10:43:31
可回收特效拖尾组件TrailRenderer轨迹折线问题
2019-12-29 10:43:32
解决button阻挡scrllrect滑动的问题
2019-12-29 10:43:32
DoTween的DoKill可能的坑点梳理
2019-12-29 10:43:32
未知轴的四元数变换问题从a到b以及从b到a
2022-03-03
3种递归写法 获取结点树目录
2022-03-03
2021年7月10日如何构建一个简单的trigger系统
2022-03-03
不使用递归算法找结点||组件
2022-03-03
从数组中找出第n大的数
2022-03-03