notify()的唤醒机制
前言
学过java的小伙伴都知道,我们在学习过程中,在学习并发多线程时,有一个notify()方法,当时博主在学习的时候,是说notify()随机唤醒一个等待中的线程并获取锁。相信许多小伙伴和博主一样,但是事实真的是这样吗?
notify()
先上一段简单的线程wait()与notify()方法。方法的逻辑就不用我讲了吧。。。
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Main {
private static List<String> waitList = new LinkedList<>();
private static List<String> notifyList = new LinkedList<>();
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 50; i++) {
String threadName = String.valueOf(i);
new Thread(() -> {
synchronized (lock) {
String cthreadNmae = Thread.currentThread().getName();
System.out.println("线程:" + cthreadNmae + "正在等待");
waitList.add(cthreadNmae);
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + cthreadNmae + "被唤醒");
notifyList.add(cthreadNmae);
}
}, threadName).start();
TimeUnit.MILLISECONDS.sleep(50);
}
TimeUnit.SECONDS.sleep(1);
for (int i = 1; i <= 50; i++) {
synchronized (lock) {
lock.notify();
TimeUnit.MILLISECONDS.sleep(10);
}
}
TimeUnit.SECONDS.sleep(1);
System.out.println(waitList.toString());
System.out.println(notifyList.toString());
}
}
运行以上代码:
[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]
[1, 2, 5, 4, 3, 11, 10, 9, 8, 7, 6, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 50, 49]
卧槽,notify()好像真的是随机唤醒的,就在这个时候我手贱了一下,对代码进行了一下修正:
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Main {
private static List<String> waitList = new LinkedList<>();
private static List<String> notifyList = new LinkedList<>();
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 50; i++) {
String threadName = String.valueOf(i);
new Thread(() -> {
synchronized (lock) {
String cthreadNmae = Thread.currentThread().getName();
System.out.println("线程:" + cthreadNmae + "正在等待");
waitList.add(cthreadNmae);
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + cthreadNmae + "被唤醒");
notifyList.add(cthreadNmae);
}
}, threadName).start();
TimeUnit.MILLISECONDS.sleep(50);
}
TimeUnit.SECONDS.sleep(1);
for (int i = 1; i <= 50; i++) {
synchronized (lock) {
lock.notify();
}
TimeUnit.MILLISECONDS.sleep(10);
}
TimeUnit.SECONDS.sleep(1);
System.out.println(waitList.toString());
System.out.println(notifyList.toString());
}
}
再去运行代码:
[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]
[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]
卧槽,这到底怎么回事?改动了一行代码,notify()竟然有序了?
好奇心爆棚的我决定研究下去,经过一番深思熟虑(抓耳挠腮)的思考之后,发现问题出在了sleep()上。
for (int i = 1; i <= 50; i++) {
synchronized (lock) {
lock.notify();
TimeUnit.MILLISECONDS.sleep(10);
}
}
当notify()之后,等待的线程A被唤醒等待获取锁,但是sleep()了,此时循环中的synchronized()代码块也需要获取锁,假设此时代码块获取锁,那等待的线程A进入“锁池”等待,又唤醒一个等待线程B,此时锁池有A、B两条等待线程,假设B获取到了锁,此时唤醒的顺序看来就是无序的,如果把sleep()移出去,让同步代码块暂缓拿锁,那唤醒的就是有序的啦。
为什么这样呢?
看一下notify()的源码:
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* {@code wait} methods.
* <p>
* The awakened threads will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened threads
* will compete in the usual manner with any other threads that might
* be actively competing to synchronize on this object; for example,
* the awakened threads enjoy no reliable privilege or disadvantage in
* being the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
这段注释的意思大概是:
**notify在源码的注释中说到notify选择唤醒的线程是任意的,但是依赖于具体实现的jvm.**
小伙伴们都知道,jvm实现方式有很多,最主流的就是HotsPot。
看一下在HotsPot中对notify()的实现:
synchronized的wait()方法和notify()方法是位于ObjectMonitor中。而notify()过程调用的是DequeueWaiter方法:
实际上是将等待队列中的第一个节点出队并获取锁,此时也就不难理解为什么notify()是有序的啦!