04 JAVA中park/unpark的原理以及JAVA在API层面线程状态总结

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。


参考资料

并发编程课程

java线程中timed和blocked两种状态的区别


20210303

posted @ 2021-03-03 21:11  狗星  阅读(1600)  评论(0编辑  收藏  举报
/* 返回顶部代码 */ TOP