自旋锁在生产者-消费者模型中避免假唤醒问题_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 自旋锁在生产者-消费者模型中避免假唤醒问题

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

 2013/9/18 0:59:12  liuluo129  程序员俱乐部  我要评论(0)
  • 摘要:先看一个有问题的只能轮替发生的生产者-消费者模型代码(源自http://www.iteye.com/problems/96126的问题)://生产/消费者模式publicclassBasket{Locklock=newReentrantLock();//产生Condition对象Conditionproduced=lock.newCondition();Conditionconsumed=lock.newCondition();booleanavailable=false
  • 标签:问题 消费者

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

?

class="java">//生产/消费者模式
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
  • 查看图片附件
发表评论
用户名: 匿名