`
liuluo129
  • 浏览: 116182 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

自旋锁在生产者-消费者模型中避免假唤醒问题

    博客分类:
  • java
阅读更多

先看一个有问题的只能轮替发生的生产者-消费者模型代码(源自http://www.iteye.com/problems/96126的问题):

 

//生产/消费者模式
public class Basket {
    Lock lock = new ReentrantLock();

    // 产生Condition对象
    Condition produced = lock.newCondition();
    Condition consumed = lock.newCondition();

    boolean available = false;

    public void produce() throws InterruptedException {
        lock.lock();
        try {
            if (available) {
                produced.await(); // 放弃lock进入睡眠
            }
            // 生产
            System.out.println("put");
            available = true;
            consumed.signal(); // 发信号唤醒等待这个Condition的线程
        } finally {
            lock.unlock();
        }
    }

    public void consume() throws InterruptedException {
        lock.lock();
        try {
            if (!available) {
                consumed.await(); // 放弃lock进入睡眠
            }
            /* 消费 */
            System.out.println("get");
            available = false;
            produced.signal(); // 发信号唤醒等待这个Condition的线程
        } finally {
            lock.unlock();
        }
    }
}

 测试程序:

 

 

public static void main(String[] args) throws InterruptedException {
	final Basket basket = new Basket();

	// 定义一个producer
	Runnable producer = new Runnable() {
		public void run() {
			try {
				basket.produce();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}
	};

	// 定义一个consumer
	Runnable consumer = new Runnable() {
		public void run() {
			try {
				basket.consume();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}
	};

	// 各产生10个consumer和producer
	ExecutorService service = Executors.newCachedThreadPool();
    for (int i = 0; i < 4; i++)
        service.submit(producer);
	//		Thread.sleep(2000);

    for (int i = 0; i < 4; i++)
		service.submit(consumer);

	service.shutdown();
}

 本来想要的效果是一次put之后一次get,但是实际情况可能会出现两次put,或者先出现两次get,即在没有put之前就已经有get了,更甚至还可能出现程序卡死,即出现了4次put,3次get后停住了,或者3次put、4次get后停住了的现象。

原因分析:

当经过put、get之后,假如此时available为true,对于produce()方法可能出现下面情况:

一个线程在等待lock;

一个线程处于await

此时其他线程在调用consume()方法后,会把available设为false,并发送给生产线程发送信号,来唤醒处于await()的线程,之后会调用unlock()方法,让处于等待lock()的线程去竞争这个锁。此时会出现两种情况:

1. 处于等待lock锁的线程竞争到锁

2. 处于await的线程被唤醒,获取锁

如果是第2种情况,则一切正常。但是如果是等待lock()锁的线程竞争到锁,会出现下面情况:


 由于处于await的线程后获取锁,但是此时available已经为true了,由于使用if,而不是while自旋锁,因此就会开始说的哪几种情况。

何为Java中的自旋锁:为了防止假唤醒,保存信号的成员变量放在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁(校注:这种做法要慎重,目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大)。被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false。

 

 

 

  • 大小: 35.5 KB
分享到:
评论

相关推荐

    生产者消费者模拟

    在计算机科学中,"生产者消费者问题"是一个经典的多线程同步问题,它涉及到了并发控制和资源管理。这个问题的基本设定是有一群生产者线程负责生成数据,而一群消费者线程负责处理这些数据。生产者和消费者共享一个...

    Linux下多线程并发控制的机制分析.docx

    在生产者-消费者模型中,可能需要使用互斥锁防止同时访问缓冲区,以及使用条件变量(condition variables)来同步线程的执行,例如当缓冲区满时通知生产者停止生产,或当缓冲区空时唤醒消费者进行消费。 8. **线程...

    【并发编程】简单化理解AQS和ReentrantLock.pdf

    - **示例代码**:使用`ReentrantLock`实现简单的生产者-消费者模型,展示如何在生产者和消费者之间同步数据交换。 - **性能对比**:比较`ReentrantLock`与`synchronized`关键字在特定场景下的性能差异。 ### 结论 ...

    多线程编程中条件变量和虚假唤醒(spurious wakeup)的讨论

    生产者线程会在缓冲区满时调用`wait()`,消费者线程则在缓冲区为空时调用`wait()`。在它们被唤醒时,都需要检查缓冲区的状态,以确认是否可以进行生产或消费。如果检查结果不满足条件,线程就需要再次等待。 ```cpp...

    Disruptor C++版(仅支持单生产者)

    7. 唤醒策略:Disruptor使用一种高效的唤醒机制,当消费者等待新事件时,生产者会唤醒它们,而不是让它们持续轮询,这样可以降低CPU使用率。 在使用Disruptor C++版时,你可以参考提供的示例代码来理解和实现自己的...

    27道顶尖的Java多线程、锁、内存模型面试题!.zip

    - **LinkedBlockingQueue/ArrayBlockingQueue**:线程安全的队列,适用于生产者消费者模型。 7. **并发编程最佳实践** - **避免过度同步**:只同步必要的代码区域。 - **尽量使用并发工具类**:而非手动管理锁。...

    线程面试汇总.docx

    这种队列非常适合用来实现生产者-消费者模型,在该模型中,生产者线程负责向队列中添加元素,而消费者线程负责从队列中取出元素进行处理。 ### Callable与Future - **Callable接口**类似于`Runnable`接口,但它...

    多线程编程模型API

    - **生产者消费者模型**(Chap6, Chap7):这是一个经典的多线程问题,涉及到如何在不引入数据竞争的情况下平衡生产者和消费者的速率。 - **基于select的I/O模型和多线程文件下载**(Chap8):select函数用于监听多...

    C++11 并发指南系列.docx

    生产者-消费者模型是一种经典的多线程设计模式,其中生产者线程生成数据,消费者线程消耗数据,通常通过队列作为缓冲区来协调两者。 总的来说,C++11在并发和多线程编程方面的改进极大地增强了语言的能力,使得...

    Java并发编程原理与实战

    通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与...

    Lock_Based_DS:基于锁的并发数据结构

    并发队列通常用于线程间通信,如生产者-消费者模型。可以使用阻塞队列(Blocking Queue),它通过条件变量(Condition)来实现等待和唤醒机制。当队列满时,生产者线程会被阻塞,直到有空间可用;反之,当队列空时...

    多线程相关

    2. 通信方式:wait()、notify()和notifyAll()方法,生产者消费者模型(BlockingQueue)、管程(Monitor)。 四、Java多线程源码分析 深入理解Java线程库的源码有助于优化多线程程序。例如,synchronized的实现基于 ...

    龙果 java并发编程原理实战

    第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition...

    Java 并发编程原理与实战视频

    第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition...

    龙果java并发编程完整视频

    第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition...

    java并发编程

    第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition...

    mpmc

    在多线程环境下,多个生产者线程可以同时向队列添加元素,而多个消费者线程也可以同时从中取出元素,这种设计模式提高了系统的并行处理能力。 在Java中,JDK的Concurrent包并没有提供原生的MPMC队列实现。然而,有...

Global site tag (gtag.js) - Google Analytics