java队列等待唤醒_Java深入学习29:线程等待和唤醒的两个方案

Java深入学习29:线程等待和唤醒的两个方案

模拟场景

一个门店,有一个店员,有消费者来消费商品(每次消费1件商品),有仓库人员来添加(生产)商品(每次生产1件商品),并假设库存上限是2.

基础代码实现

public classThreadNotifyTest {public static voidmain(String[] args) {

Clerk clerk= newClerk();

Producer producer= newProducer(clerk);

Consumer consumer= newConsumer(clerk);new Thread(producer,"生产者A").start();new Thread(consumer,"消费者B").start();new Thread(producer,"生产者C").start();new Thread(consumer,"消费者D").start();

}

}classClerk{private int proNum = 0;

get(){

......//参考具体方案

}

add(){

......//参考具体方案

}

}class Producer implementsRunnable{privateClerk clerk;publicProducer(Clerk clerk) {this.clerk =clerk;

}

@Overridepublic voidrun() {for(int i=0; i<10; i++){

clerk.add();

}

}

}class Consumer implementsRunnable{privateClerk clerk;publicConsumer(Clerk clerk) {this.clerk =clerk;

}

@Overridepublic voidrun() {for(int i=0; i<10; i++){

clerk.get();

}

}

}

方案1:基础版本。使用synchronized ,get 时判断是否缺货,add 时判断是否库满

问题:根据结果发现,在生产、消费过程中会出现大量的库满和缺货情况。

classClerk{private int proNum = 0;public synchronized voidget(){if(proNum <= 0){

System.out.println("缺货");

}else{

System.out.println(Thread.currentThread().getName()+ ": " + --proNum);

}

}public synchronized voidadd(){if(proNum > 1){

System.out.println("库满");

}else{

System.out.println(Thread.currentThread().getName()+ ": " + ++proNum);

}

}

}

方案2:使用 wait 和 notifyAll 方法,解决方案1问题;但是引发了新问题

问题:程序会出现无法终止的情况。原因在于当循环操作在进行到最后时,会出现一个线程一直在等待,没有其他线程唤醒该等待线程。

classClerk{private int proNum = 0;public synchronized voidget(){if(proNum <= 0){

System.out.println("缺货");try{this.wait();

}catch(InterruptedException e) {

e.printStackTrace();

}

}else{

System.out.println(Thread.currentThread().getName()+ ": " + --proNum);try{this.notifyAll();

}catch(Exception e) {

e.printStackTrace();

}

}

}public synchronized voidadd(){if(proNum > 1){

System.out.println("库满");try{this.wait();

}catch(InterruptedException e) {

e.printStackTrace();

}

}else{try{this.notifyAll();

}catch(Exception e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ ": " + ++proNum);

}

}

}

方案3:我们对get和add方法内部进行了优化,去掉了if-else中的else;解决了线程一直等待问题;但同时引入了新的问题;

问题:库存会溢出或者为负数;原因是多个生产者和消费者修改库存,产生了脏数据(比如两个生产者同时被欢迎,执行了++proNum,导致库存溢出);这也是常说的虚假唤醒情况。Object类中的wait()方法也专门针对该情况做了推荐处理(参考方案4)。

20200707173959850805.png

class Clerk{

private int proNum = 0;

public synchronized void get(){

if(proNum <= 0){

System.out.println("缺货");

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName() + ": " + --proNum);

try {

this.notifyAll();

} catch (Exception e) {

e.printStackTrace();

}

}

public synchronized void add(){

if(proNum > 1){

System.out.println("库满");

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

try {

this.notifyAll();

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ": " + ++proNum);

}

}

方案4:使用 while 替换 if 判断,解决虚假唤醒问题

classClerk{private int proNum = 0;public synchronized voidget(){while(proNum <= 0){

System.out.println("缺货");try{this.wait();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName()+ ": " + --proNum);try{this.notifyAll();

}catch(Exception e) {

e.printStackTrace();

}

}public synchronized voidadd(){while(proNum > 1){

System.out.println("库满");try{this.wait();

}catch(InterruptedException e) {

e.printStackTrace();

}

}try{this.notifyAll();

}catch(Exception e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ ": " + ++proNum);

}

}

方案5:使用 Lock 锁和 Condition

classClerk{private int proNum = 0;private Lock lock = newReentrantLock();private Condition condition =lock.newCondition();public voidget(){

lock.lock();try{while(proNum <= 0){

System.out.println("缺货");try{

condition.await();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName()+ ": " + --proNum);try{

condition.signalAll();

}catch(Exception e) {

e.printStackTrace();

}

}finally{

lock.unlock();

}

}public voidadd(){

lock.lock();try{while(proNum > 1){

System.out.println("库满");try{

condition.await();

}catch(InterruptedException e) {

e.printStackTrace();

}

}try{

condition.signalAll();

}catch(Exception e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ ": " + ++proNum);

}finally{

lock.unlock();

}

}

}

总结

1- 方案4和方案5,针对模拟场景,是比较合理的方案。

几个概念:

1- Java中对象锁的模型

java中对象锁的模型,JVM会为一个使用内部锁(synchronized)的对象维护两个集合,Entry Set和Wait Set,也有人翻译为锁池和等待池,意思基本一致。对于Entry Set:如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。对于Wait Set:如果线程A调用了wait()方法,那么线程A会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。还有需要注意的是,某个线程B想要获得对象锁,一般情况下有两个先决条件,一是对象锁已经被释放了(如曾经持有锁的前任线程A执行完了synchronized代码块或者调用了wait()方法等等),二是线程B已处于RUNNABLE状态。那么这两类集合中的线程都是在什么条件下可以转变为RUNNABLE呢?对于Entry Set中的线程,当对象锁被释放的时候,JVM会唤醒处于Entry Set中的某一个线程,这个线程的状态就从BLOCKED转变为RUNNABLE。对于Wait Set中的线程,当对象的notify()方法被调用时,JVM会唤醒处于Wait Set中的某一个线程,这个线程的状态就从WAITING转变为RUNNABLE;或者当notifyAll()方法被调用时,Wait Set中的全部线程会转变为RUNNABLE状态。所有Wait Set中被唤醒的线程会被转移到Entry Set中。然后,每当对象的锁被释放后,那些所有处于RUNNABLE状态的线程会共同去竞争获取对象的锁,最终会有一个线程(具体哪一个取决于JVM实现,队列里的第一个?随机的一个?)真正获取到对象的锁,而其他竞争失败的线程继续在Entry Set中等待下一次机会。

2- wait()方法外面为什么是while循环而不是if判断

我们在调用wait()方法的时候,心里想的肯定是因为当前方法不满足我们指定的条件,因此执行这个方法的线程需要等待直到其他线程改变了这个条件并且做出了通知。那么为什么要把wait()方法放在循环而不是if判断里呢,其实答案显而易见,因为wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行指定条件的判断,以决定是满足条件往下执行呢还是不满足条件再次wait()呢。

3- notifyAll() 和 notify() 区别

notify()是唤醒一个线程,notifyAll()是唤醒全部线程。notify()非常容易导致死锁,

附录1-方案日志

20200707173959980693.png

END

1acf1ba0b2da598d3372dd574cf1d46f.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
时下带有语音助手功能的智能网关产品越来越流行,比如小米的智能音箱,天猫精灵等等互联网公司都纷纷推出基于智能家居环境应用的网关产品包括家用的节点设备。这种网关最大的体现了语音识别技术,基于云端的数据安全技术,同时又结合Wi-Fi技术实现一系列智能化应用产品的互联互通。目前市面上销售的产品基本上都是以MTK以及Rockchip,Realtek等等成本较高的方案为主。出于成本因素,Microchip推出了一款基于ATSAMG55J19A-MU+WINC1500B为主的完整的解决方案,该方案在Amazon已经形成产品化。 对于基于Amazon Alexa 的Microchip语音助手解决方案。结合Amazon电商购物平台,又支持Amazon一键WiFi购物的功能,一键Wi-Fi购物,就是您可以按下自动定购已经标记的产品来购买商品,同时也可以对其直接语音告诉它你要想购买的商品。 Amazon New Amazon Dash Wand “魔棒”已经量产,相信国内的电商巨头也会将这种便捷快捷的购物方式引入到国内市场。 核心技术优势1. 150mS快速回连AP 2. 超300款AP 兼容测试 3. 支持AWS ,阿里云,微软云 4. 提供主机端驱动原码 5. 提供多种安全连接云应用的参考例子 方案规格ATSAMG55: 1:Cortex-M4内核,FPU。主频可达到120MHz。 2:超低功耗 (1)在运行模式只有 100 µA/MHz。 (2)在深度睡眠SRAM保持模式下: 7 µA 。 (3)从深度睡眠到工作模式,唤醒时间只需要 3 µs。 3:Flash:512KB/SRAM:176KB。 4:工作电压:1.62V to 3.6V 5:温度范围:-40 ℃to 85℃ 6:片上集成UART,ADC.SPI,USB等丰富的外设接口。 WINC1500: 1:支持2.4GHz b/g/n IoT网络控制器。 2:超低功耗。 3:工作电压: 3.0V to 4.2V。 4:接口: SPI。 5:支持安全协议: WPA/WPA2 Personal, TLS, SSL。 6:支持网络服务: DHCP, DNS, TCP/IP (IPv4), UDP, HTTP, HTTPS。 7:Chip 和 PCB 天线(ATWINC1500-MR210PBxxxx) 8:Chip 和u.FL (ATWINC1500-MR210UBxxxx) 9:ATWINC1500-MR210PBxxxx (4Mb flash)和ATWINC1510-MR210PBxxxx (8Mb flash)。 10:开发环境支持Atmel Studio 7 - 可查阅ASF笔记。 11:认证: in the US, Canada, Europe, Japan, Korea, China, India 和Taiwan。 方案来源于大大通。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值