多线程编程中要特别注意可能发生死锁的场景,java多线程中与锁相关的几个方法有wait()/notify()/sleep()/yeild()等。
这里由一个死锁为题引出几个方法使用时应注意的问题。
死锁案例:notify和notifyAll
先来看一段程序:
1 | class PubSub { |
- 执行这段程序,发现大部分情况都不会打印1000条记录,会在中间卡住,发生了死锁。
- 原因分析:
- pub和sub都是同步方法,所以多个调用sub和sub的线程都会处于阻塞状态,等待一个正在运行的线程去唤醒他们。
- notify方法只能唤醒一个线程,其它等待的线程仍然处于wait状态。假设调用sub方法的线程执行完后,这时如果唤醒的是一个sub方法的调度线程,那么while循环等于true,则此唤醒的线程也会处于等待状态,此时所有的线程都处于等待状态,那么也就没有了运行的线程来唤醒它们,这就发生了死锁。
- 解决方法:
- 使用notifyAll()
- notifyAll方法来唤醒所有正在等待该锁的线程,那么所有的线程都会处于运行前的准备状态(就是sub方法执行完后,唤醒了所有等待该锁的状态,注:不是wait状态),那么此时,即使再次唤醒一个sub方法调度线程,while循环等于true,唤醒的线程再次处于等待状态,那么还会有其它的线程可以获得锁,进入运行状态。
- notify与notifyAll区别
- notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
- notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
Java sleep/notify/wait/yield
sleep和yield等并没有释放锁,而wait释放了锁。
只能在同步控制方法或同步块中调用wait()、notify()和notifyAll()。
使用notify和wait可以让多个线程间相互协作,以控制线程运行,以下示例为使用Controller对Monitor进行控制。
1 | class Controller implements Runnable { |
通过Controller来控制条件变量go
,开始时所有的Monitor均处于wait,等到Controller将go置为true后,所有Monitor被唤醒。
注意这里使用的是notifyAll
,使用notify
会发生死锁。
这个示例的本质是“忙等待”,等条件达到了再运行,它还有另一种写法。
1 | class Controller implements Runnable { |
注意:
- watching方法不再是同步方法,想想为什么?注意go其实为volatile类型,并不需要强制去同步。
- goMessage方法不再需要notifyAll
其他死锁案例
synchronized与ReentrantLock读写锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A simple thread task representation
* @author Pierre-Hugues Charbonneau
*
*/
public class Task {
// Object used for FLAT lock
private final Object sharedObject = new Object();
// ReentrantReadWriteLock used for WRITE & READ locks
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/**
* Execution pattern #1
*/
public void executeTask1() {
// 1. Attempt to acquire a ReentrantReadWriteLock READ lock
lock.readLock().lock();
// Wait 2 seconds to simulate some work...
try { Thread.sleep(2000);}catch (Throwable any) {}
try {
// 2. Attempt to acquire a Flat lock...
synchronized (sharedObject) {}
}
// Remove the READ lock
finally {
lock.readLock().unlock();
}
System.out.println("executeTask1() :: Work Done!");
}
/**
* Execution pattern #2
*/
public void executeTask2() {
// 1. Attempt to acquire a Flat lock
synchronized (sharedObject) {
// Wait 2 seconds to simulate some work...
try { Thread.sleep(2000);}catch (Throwable any) {}
// 2. Attempt to acquire a WRITE lock
lock.writeLock().lock();
try {
// Do nothing
}
// Remove the WRITE lock
finally {
lock.writeLock().unlock();
}
}
System.out.println("executeTask2() :: Work Done!");
}
public ReentrantReadWriteLock getReentrantReadWriteLock() {
return lock;
}
}该实例的详细分析