最近在学习Java多线程设计的时候,在网上看到一个面试题目的讨论,虽然楼主所说有些道理,但感觉还是有些问题,故此在和同事讨论以后还是有了若干收获,在此略作总结。
首先,来看看这个面试题目吧。
- 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() throws InterruptedException {
- synchronized (this) {
- if (list.size() <= 0) {
- wait();
- }
- return list.remove(list.size() - 1);
- }
- }
- }
代码分析:
从整体上,在并发状态下,push和pop都使用了synchronized的锁,来实现同步,同步的数据对象是基于List的数据;大部分情况下是可以正常工作的。
问题描述:
状况1:
1. 假设有三个线程: A,B,C. A 负责放入数据到list,就是调用push操作, B,C分别执行Pop操作,移除数据。
2. 首先B先执行,于pop中的wait()方法处,进入waiting状态,进入等待队列,释放锁。
3. A首先执行放入数据push操作到List,在调用notify()之前; 同时C执行pop(),由于synchronized,被阻塞,进入Blocked状态,放入基于锁的等待队列。注意,这里的队列和2中的waiting等待队列是两个不同的队列。
4. A线程调用notify(),唤醒等待中的线程A。
5. 如果此时, C获取到基于对象的锁,则优先执行,执行pop方法,获取数据,从list移除一个元素。
6. 然后,A获取到竞争锁,A中调用list.remove(list.size() - 1),则会报数据越界exception。
状况2:
1. 相同于状况1
2. B、C都处于等待waiting状态,释放锁。等待notify()、notifyAll()操作的唤醒。
3. 存在被虚假唤醒的可能。
何为虚假唤醒?
虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的。
解决的办法是基于while来反复判断进入正常操作的临界条件是否满足:
- synchronized (obj) {
- while (<condition does not hold>)
- obj.wait();
- ... // Perform action appropriate to condition
- }
#1. 使用可同步的数据结构来存放数据,比如LinkedBlockingQueue之类。由这些同步的数据结构来完成繁琐的同步操作。
#2. 双层的synchronized使用没有意义,保留外层即可。
#3. 将if替换为while,解决虚假唤醒的问题。
Java Thread的状态
具体参看java.lang.Thread.State中具体的定义,共6中状态。
1. New
至今尚未启动的线程的状态
2. RUNNABLE
可运行线程的线程状态。处于可运行状态的某一线程正在 Java 虚拟机中运行,但它可能正在等待操作系统中的其他资源,比如处理器。
3. Blocked
受阻塞并且正在等待监视器锁的某一线程的线程状态。处于受阻塞状态的某一线程正在等待监视器锁,以便进入一个同步的块/方法,或者在调用 Object.wait 之后再次进入同步的块/方法。
4. Waiting
某一等待线程的线程状态。某一线程因为调用下列方法之一而处于等待状态:
- 不带超时值的 Object.wait
- 不带超时值的 Thread.join
处于等待状态的线程正等待另一个线程,以执行特定操作。 例如,已经在某一对象上调用了 Object.wait() 的线程正等待另一个线程,以便在该对象上调用 Object.notify() 或 Object.notifyAll()。已经调用了 Thread.join() 的线程正在等待指定线程终止。
5. Timed_Waiting
具有指定等待时间的某一等待线程的线程状态。某一线程因为调用以下带有指定正等待时间的方法之一而处于定时等待状态:
- Thread.sleep
- 带有超时值的 Object.wait
- 带有超时值的 Thread.join
- LockSupport.parkNanos
- LockSupport.parkUntil
已终止线程的线程状态。线程已经结束执行。
Java Thread状态转换图示:
=
第二张图示,从阻塞状态队列的角度来看Thread状态的变化:
通过以上2个状态转换图的展示,我相信大家对这个线程的状态变化有了自己的理解。
什么是锁?
基于Java对象,而产生的互斥锁,比如synchronized包装的方法和代码块。
Blocked vs Waiting/Timed_Waiting
进入Blocked状态,是在竞争锁的过程中,比如synchronized导致的竞争。从而进入blocked状态。
Waiting/Timed_Waiting是在调用了wait()之后,进入了waiting状态。待notify()/notifyAll()之后,就进入Blocked状态。然后竞争锁,从而可以获取处理机,进行执行操作。
Notify() vs NotifyAll()
notify: 从waiting等待队列中,随机选取一个线程进行唤醒。
notifyAll: 将改对象的waiting等待队列中的所有线程唤醒,并入blocked状态。然后由其自由竞争锁,依次执行,最终直到所有的线程都依次获取锁,并执行完毕。队列中就没有等待的线程。
参考资料:
1. http://my.oschina.net/u/176507/blog/137880
2. http://docs.oracle.com/javase/7/docs/api/
3. Java Thread 状态 http://shihaiyang.iteye.com/blog/437902
4. Java Thread 状态转换图 http://my.oschina.net/mingdongcheng/blog/139263
5. 虚假唤醒 http://en.wikipedia.org/wiki/Spurious_wakeup
-
顶
- 0
-
踩