1 park与unpark的使用以及原理
1-1 基本使用
- park/unpark并非线程类的方法,是concurrent的方法
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
实例:
package chapter4;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.LockSupport;
import chapter2.Sleeper;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.test3")
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("start...");
try {
Thread.sleep(1000); // 睡眠时间1
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("park...");
LockSupport.park();
log.warn("resume...");
},"t1");
t1.start();
Thread.sleep(2000);
log.warn("unpark..."); //睡眠时间2
LockSupport.unpark(t1);
}
}
运行结果1(设置睡眠时间1为1000ms,睡眠时间2为2000ms,即unpark在park之后执行)
[t1] WARN c.test3 - park...
[main] WARN c.test3 - unpark...
[t1] WARN c.test3 - resume...
运行结果2(设置睡眠时间1为2000ms,睡眠时间2为1000ms,即park在unpark之后执行)
[main] WARN c.test3 - unpark...
[t1] WARN c.test3 - park...
[t1] WARN c.test3 - resume...
总结:上面的2个结果说明,park与unpark成对使用时,对使用前后的次序并不敏感。原因见原理部分。
1-2 park/unpark与wait/notify的区别
- wait/notify必须在有锁的情况下使用(需要关联Monitor对象),park/unpark没有这个限制条件。
- park/unpark配对使用能够精确的指定具体的线程的阻塞/运行,notify只能随机唤醒一个线程
- park/unpark配对使用可以先unpark,wait/notify配合使用不能够先notify。
1-3 park/unpark的底层原理
1-3-1 先park后unpark的场景分析
step1: 线程0在执行的过程中调用park方法。
step2:检查_counter是否为0
- 为0,获得_mutex互斥锁
- 为1,说明之前其他线程调用过park方法,则将_counter设为1后线程继续执行。(先unpark后park的场景)
step3:获得互斥锁之后,线程进入 _cond 条件变量阻塞
step4: 某线程在执行的过程中调用unpark方法后,设置_counter为1。
step5:唤醒 _cond 条件变量中的 Thread_0
step6:Thread_0 恢复运行 ,并恢复_counter为0。
1-3-2 先unpark后park的场景分析
step1: 某线程调用unpark方法后,_counter被设置为1。
step2:线程0执行过程中调用park方法,检查_counter是1,无法获得互斥变量__mutex进入阻塞队列
step3:线程0恢复_counter为0并继续执行。
总结
从2个例子中可以看到,park方法调用后,必须满足_counter为0,才能进入阻塞队列。如果在park之间调用unpark,那么park方法就会失效,无法让线程停止运行。
2 JAVA中API层面的线程10种状态转换
2-1 六种状态回顾
NEW:线程刚刚被创建时,还没有start()的状态
RUNABLE: Java中的RUNABLE包含了操作系统层面的运行,阻塞,可运行状态。
- 操作系统层面的线程的运行,阻塞等在Java层面无法体现出来。
BLOCKED,WAITING,TIMED_WAITINGJava API层面的阻塞
- TIMED_WAITING:使用sleep方法可能会出现
- WAITING: 使用join方法后可能会出现
- BLOCKED:使用synchronize方法可能会出现
2-2 状态的转换10种情况分析
假设有线程t。
情况1 NEW --> RUNNABLE
- 线程t被定义后,其状态为NEW,当调用 t.start() 方法时,由 NEW --> RUNNABLE
情况2,3,4 RUNNABLE <--> WAITING
wait/notify|wait/interrupt
- 线程t何时从RUNNABLE变为WAITING?
- t在执行过程中通过synchroized(obj)获取到锁(拥有了monitor的owner),然后调用obj.wait()方法,则线程进入WAITING状态(进入到monitor的waitset等待)。
- 线程t何时从WAITING变为RUNABLE?
- 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时(notify或者使用中断)。
- 竞争锁成功,t 线程从 WAITING --> RUNNABLE
- 竞争锁失败,t 线程从 WAITING --> BLOCKED (进入到monitor的entryList等待)
join|join/interrupt(可以将“当前线程”作为“主线程”)
- 当前线程何时从RUNNABLE变为WAITING?
- 当前线程(比如主线程)需要等待t线程运行结束,调用join()的时候(此时调用线程会进入到线程t关联的monitor中的waitset进行等待)。
- 当前线程何时从WAITING变为RUNABLE?
- t 线程运行结束 (猜测:线程t运行结束,其作为锁对象关联的monitor被回收,在monitor上等待的当前线程不会再等待又继续执行)
- t线程调用当前线程的interrupt方法
park/unpark| park/interrupt(可以将当前线程作为主线程)
- 当前线程何时从RUNNABLE变为WAITING?
- 当前线程调用 LockSupport.park() 方法
- 当前线程何时从WAITING变为RUNABLE?‘
- t线程调用了LockSupport.unpark(目标线程)
- t线程调用了当前线程的interrupt方法
情况5,6,7,8 RUNNABLE <--> TIMED_WAITING(结合情况2,3,4看)
wait(n)/notify|wait(n)调用/等待足够时间|wait(n)/interrupt
join(n)/interrupt|join(n)调用后/等待足够时间
sleep(n)/等待足够时间
parkNanos(nanos),parkUntil(millis) /unpark(目标线程) 或者interrupt或者等待超时
情况9 RUNNABLE <--> BLOCKED
- t线程何时从 RUNNABLE变为BLOCKED?
- t 线程用 synchronized(obj) 尝试获取对象锁时但是竞争失败 (此时线程t会进入对象头的阻塞队列)
- 当前线程何时从BLOCKED变为RUNNABLE?‘
- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,其中 t 线程竞争成功。
情况 10 RUNNABLE <--> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
2-3 WAITING与BLOCKED的区别(从图中分辨)
总结:从上面的图可以看出WAITING线程与BLOCKED线程在不同地方。WAITING线程在对象头的WaitSet,而BLOCKED线程在对象头的EntryList。
- waiting:主动为之,wait()方法释放cpu执行权和释放锁进入对象头的Waitset ,需要notify()唤醒,然后进入到EntryList等待竞争锁。
- blocked:被动的,线程在竞争锁的时候失败,被阻塞,进入EntryList。
参考资料