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

先看一个有问题的只能轮替发生的生产者-消费者模型代码(源自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()锁的线程竞争到锁,会出现下面情况:

自旋锁在生产者-消费者模型中避免假唤醒问题_第1张图片
 由于处于await的线程后获取锁,但是此时available已经为true了,由于使用if,而不是while自旋锁,因此就会开始说的哪几种情况。

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

 

 

 

你可能感兴趣的:(java,多线程,生产者-消费者,自旋锁)