wait/notify原理
wait/notify是本地方法,不是jdk实现的,所以看不到它的源码。
wait/notify是基于synchronized的,所以先介绍synchronized,synchronized修饰到对象之后,锁对象会关联一个Moniror对象:
- WaitSet是等待队列,当前线程获取到锁对象后调用wait方法后会进入该WaitSet等待队列。
- EntryList是阻塞队列,当前线程获取不到该锁对象会进入该EntryList阻塞队列。
- Owner,当前线程获取到锁对象,并正常执行。
wait/notify原理:
- 就是当前线程前线程进入WaitSet等待队列,状态由Runnable–>Waiting,即调用了wait()。
- 接着当前线程可以去竞争锁,竞争成功则去运行;竞争失败后进入EntryList阻塞队列,状态由WaiTing–>Runnable或者WaiTing–>Runnable–>Blocked,即调用notify()/notifyall()后,被notify的线程会从WaitSet等待队列里面出来。
join原理
底层使用的是保护性暂停模式,对join的线程对象底层调用的isAlive()和wait()方法。
部分源码:
- 最简单的join()调用。
public final void join() throws InterruptedException {
join(0);
}
- join(0)调用。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join原理:
- 说白了就是不停地使用isAlive()去判断当前线程是否还是存活,存活则让监控线程随着while循环阻塞在此,结束则跳出循环,监控线程往下运行。简单来说就是使用while阻塞监控线程 。
- isAlive()是一个本地方法,判断当前线程是否还存活。使用wait()是为了降低对cpu的使用率。
park/unpark原理
LockSupport.park()和LockSupport.unpark(Thread thread)是本地方法,不是jdk实现的,所以看不到它的源码。
park/unpark原理:
- 每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond (条件)和 _mutex (互斥量)。
- 当调用park时,先尝试能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回。
- 当unpark时,直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。
使用小故事去理解:
线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
调用 park 就是要看需不需要停下来歇息
如果备用干粮耗尽,那么钻进帐篷歇息
如果备用干粮充足,那么不需停留,继续前进
调用 unpark,就好比令干粮充足
如果这时线程还在帐篷,就唤醒让他继续前进
如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮s