多线程虚假唤醒

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LuckyBug007/article/details/70053669

问题:这段代码大多数情况下运行正常,但是某些情况下会出问题。什么时候会出现什么问题?如何修正?

public class MyStack {
    private List<String> list = new ArrayList<String>();

    public synchronized void push(String value) {
        synchronized (this) {
            list.add(value);
            notify();
        }
    }

    public synchronized String pop() {
        synchronized (this) {// 双层synchronized无意义
            if (list.size() <= 0) {
                try {
                	wait();
            	} catch (InterruptedException e) {
                	e.printStackTrace();
            	}
            }
            return list.remove(list.size() - 1);
        }
    }
}

代码分析:

从整体上,在并发状态下,push和pop都使用了synchronized的锁,来实现同步,同步的数据对象是基于List的数据;大部分情况下是可以正常工作的。

问题描述:

1.  假设有三个线程: A,B,C。A负责放入数据到list,调用push操作;B、C分别执行pop操作,移除数据;

2.  首先B线程先执行,列表空,于pop中的wait()方法处,进入waiting状态,B进入等待队列,释放锁;

3.  接着A执行放入数据push操作到list;

4.  A调用notify()之前,同时C执行pop(),由于synchronized,被阻塞,进入Blocked状态,C进入基于锁的等待队列注意,这里的队列和2中的waiting等待队列是两个不同的队列;

5.  接着A线程调用notify(),唤醒等待中的线程B;

6.  如果此时,C优先获取到基于对象的锁,则优先执行,执行pop方法,获取数据,从list中移除一个元素;

7.  接着B获取到竞争锁,由于C线程“偷走了”A放入的元素,B中调用list.remove(list.size() - 1),则会报数据越界Exception,那么对于B线程的唤醒就是“虚假”的。

何为虚假唤醒?

虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的。

解决的办法是基于while循环来反复判断进入正常操作的临界条件是否满足

synchronized (obj) {  
	while (<condition does not hold>)  
		obj.wait();  
	... // Perform action appropriate to condition  
}
上例中的pop函数可如下修改:
public synchronized String pop() {
        while (list.size() <= 0) {// while代替if
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return list.remove(list.size() - 1);
    }

如何修复问题?

#1.  使用可同步的数据结构来存放数据,比如LinkedBlockingQueue之类。由这些同步的数据结构来完成繁琐的同步操作。

#2.  双层的synchronized使用没有意义,保留外层即可。

#3.  将if替换为while,解决虚假唤醒的问题。

展开阅读全文

没有更多推荐了,返回首页