一、概述
前段时间去面试,面试官问到一个问题:
当时看到这个 首先肯定是想到了 线程同步的问题,想让两个异步线程,做到同步某个操作,必然是synchronized 。 可是单纯的对某个方法或者是对象加锁是锁不住的,那该如何解决上述问题呢? 当时各种被鄙视...
二、如何正确处理上述问题
首先我们看一下我们想到的synchronized 的具体含义 :
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
synchronized 顾名思义 就同步的意思,我的理解是为了线程安全防止并发产生 同一时间只能有一个线程可以访问该代码段,比如我们的单例代码
public class ImageUtil {
private static ImageUtil mImageUtil;
public static synchronized ImageUtil getInstance(){
if (mImageUtil==null) {
mImageUtil = new ImageUtil();
}
return mImageUtil;
}
private ImageUtil() {}
}
上述单例模式synchronized 就是为了防止并发、线程安全的代码。但是我如何让解决上述问题呢? 好吧!我想到了 阻塞B线程 等到A线程执行完A1后我们在执行B1。
网上有人说在B1方法前写一个 while 死循环堵塞B线程等待A1执行完毕,满足条件后跳出循环执行B1,O了! 这样确实可以达到我们想要的效果。不过显然这样的设计是不合理的而且也比较消耗性能。那正确的我们应该如何做呢?
既然是线程间的阻塞问题,我们想到了wait(),notify()和notifyAll() 先看一下这三个方法的解释:
wait(),notify()和notifyAll()都是java.lang.Object的方法:
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
这三个方法,都是Java语言提供的实现线程间阻塞(Blocking)和控制进程内调度(inter-process communication)的底层机制。在解释如何使用前,先说明一下两点:
1. 正如Java内任何对象都能成为锁(Lock)一样,任何对象也都能成为条件队列(Condition queue)。而这个对象里的wait(), notify()和notifyAll()则是这个条件队列的固有(intrinsic)的方法。
2. 一个对象的固有锁和它的固有条件队列是相关的,为了调用对象X内条件队列的方法,你必须获得对象X的锁。这是因为等待状态条件的机制和保证状态连续性的机制是紧密的结合在一起的。
(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
这三个方法,都是Java语言提供的实现线程间阻塞(Blocking)和控制进程内调度(inter-process communication)的底层机制。在解释如何使用前,先说明一下两点:
1. 正如Java内任何对象都能成为锁(Lock)一样,任何对象也都能成为条件队列(Condition queue)。而这个对象里的wait(), notify()和notifyAll()则是这个条件队列的固有(intrinsic)的方法。
2. 一个对象的固有锁和它的固有条件队列是相关的,为了调用对象X内条件队列的方法,你必须获得对象X的锁。这是因为等待状态条件的机制和保证状态连续性的机制是紧密的结合在一起的。
(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting
for state-based conditions is necessarily tightly bound to the mechanism fo preserving state consistency)
根据上述两点,在调用wait(), notify()或notifyAll()的时候:
必须先获得锁,且状态变量须由该锁保护,而固有锁对象与固有条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的。
好了,说了一堆解释,那咱们改如何用代码体现呢?
1. 执行wait, notify时,不获得锁会如何?
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
obj.notifyAll();
}
执行以上代码,会抛出java.lang.IllegalMonitorStateException的异常。
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
obj.notifyAll();
}
执行以上代码,会抛出java.lang.IllegalMonitorStateException的异常。
2. 执行wait, notify时,不获得该对象的锁会如何?
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Object lock = new Object();
synchronized (lock) {
obj.wait();
obj.notifyAll();
}
}
执行代码,同样会抛出java.lang.IllegalMonitorStateException的异常。
3. 为什么在执行wait, notify时,必须获得该对象的锁?
这是因为,如果没有锁,wait和notify有可能会产生竞态条件(Race Condition)。考虑以下生产者和消费者的情景:
1.1生产者检查条件(如缓存满了)-> 1.2生产者必须等待
2.1消费者消费了一个单位的缓存 -> 2.2重新设置了条件(如缓存没满) -> 2.3调用notifyAll()唤醒生产者
我们希望的顺序是: 1.1->1.2->2.1->2.2->2.3
但在多线程情况下,顺序有可能是 1.1->2.1->2.2->2.3->1.2。也就是说,在生产者还没wait之前,消费者就已经notifyAll了,这样的话,生产者会一直等下去。
所以,要解决这个问题,必须在wait和notifyAll的时候,获得该对象的锁,以保证同步。
正确写法:
private Thread B;
private Thread A;
private boolean a = false;
public void t() {
B = new Thread(new Runnable() {
public void run() {
synchronized (B) {
try {
if (!a) {
System.out.println(a + "------线程B===wait前==》"
+ System.currentTimeMillis());
B.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (a) {
System.out.println(a + "线程B==wait后===》"
+ System.currentTimeMillis());
}
}
}
});
B.start();
A = new Thread(new Runnable() {
public void run() {
try {
A.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A=====》" + System.currentTimeMillis());
synchronized (B) {
a = true;
B.notifyAll();
}
}
});
A.start();
}
先看一下运行结果:
上述代码在t()方法中运行了2个异步线程A、B ,线程A模拟了一个5秒的延时操作,当boolean 型变量 a 默认为false 当B线程执行时 首先打印了
进入 wait()等待阻塞状态,当A线程结束休眠时执行了
而后对变量a赋值为 true后唤醒B线程a满足条件执行
。好了,逻辑就是这样,完美解决上述问题。
三、扩展
private void b() {
System.out.println("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb方法");
}
private Thread D;
private Thread C;
private Thread B;
private Thread A;
private boolean a = false;
public void t() {
C = new Thread(new Runnable() {
public void run() {
}
});
C.start();
B = new Thread(new Runnable() {
public void run() {
D = new Thread(new Runnable() {
public void run() {
try {
D.sleep(1000);
synchronized (C) {
C.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("DDDDDDDDDDDDDDDDDDDD===wait后=》"
+ System.currentTimeMillis());
}
});
D.start();
synchronized (C) {
try {
if (!a) {
System.out.println(a + "------线程B===wait前==》"
+ System.currentTimeMillis());
C.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (a) {
System.out.println(a + "------线程B==wait后===》"
+ System.currentTimeMillis());
}
b();
}
}
});
B.start();
A = new Thread(new Runnable() {
public void run() {
try {
A.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A=====》" + System.currentTimeMillis());
synchronized (C) {
a = true;
C.notifyAll();
}
System.out.println("线程A==notify===》"
+ System.currentTimeMillis());
}
});
A.start();
}
A B C D 4个线程 C B A 三个线程,D线程是B线程中的子线程:
运行结果: ![]()
![]()
![]()






上述代码的执行结果,前两条是必然先执行的。notifyAll() 会释放某个线程的所有锁,剩下的没有执行顺序只是抢占CUP了,两个异步线程释也是如此了。
四、总结
2、wait notity 必须是在synchronized(方法或对象)中执行
3、任何线程的 wait 在 某个线程中执行只是为了 阻塞该线程
4、任何线程的 notity 在 某个线程中执行只是为了 释放该线程
5、notifyAll 会唤醒某个线程的所有锁 !!!也算是第一次写博客了,欢迎大家吐槽!