并发
1.进程与线程
1.1 进程
- 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
- 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)
1.2 线程
- 一个进程之内可以分为一到多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行 。
- Java 中,线程作为小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作 为线程的容器
1.3 两者对比
-
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
-
进程拥有共享的资源,如内存空间等,供其内部的线程共享
-
进程间通信较为复杂
-
同一台计算机的进程通信称为 IPC(Inter-process communication)
-
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
-
-
线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
-
线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
2. 并行和并发
2.1 并发
-
单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感 觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 , 一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent
-
CPU 时间片一 时间片二 时间片三 时间片四 core 线程一 线程二 线程三 线程四 -
共享变量+读写操作+多线程=临界区
2.2并行
-
多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。
2.3 应用(异步)
-
以调用方角度来讲,如果
-
需要等待结果返回,才能继续运行就是同步
-
不需要等待结果返回,就能继续运行就是异步
-
设计
- 多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如 果没有线程调度机制,这 5 秒 cpu 什么都做不了,其它代码都得暂停…
-
结论
- 比如在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程
- tomcat 的异步 servlet 也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程
- ui 程序中,开线程进行其他操作,避免阻塞 ui 线程
-
提高效率
- 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用 cpu ,不至于一个线程总占用 cpu,别的线程没法干活
- 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任 务都能拆分(参考后文的【阿姆达尔定律】) 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意
- IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一 直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化,(这种情况虽然不占用CPU,但是会使线程一直阻塞,使线程利用率降低,而非阻塞和异步可以有效地提升线程的利用率);CPU只需要通知DMA就可以,然后等到数据准备好之后触发中断,告知CPU数据准备好了
3. JAVA线程
3.1 创建和运行线程
方法一:直接使用Thread
-
直接使用Thread
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; /** * @Author: sunyang * @Date: 2021/7/28 * @Description: */ @Slf4j(topic = "c.CreateThreadDemo") public class CreateThreadDemo { public static void main(String[] args) { Thread thread = new Thread(() -> log.debug("running")); thread.setName("thread-0"); thread.start(); log.debug("running"); } }
-
方法二:使用Runnable
-
把线程和任务(要执行的代码)分开
-
Thread代表线程
-
Runnable可运行的任务(线程要执行的代码)
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-07-28 19:18 **/ @Slf4j(topic = "c.RunnableDemo") public class RunnableDemo { public static void main(String[] args) { Runnable runnable = () -> log.debug("runnable-running"); Thread thread = new Thread(runnable, "thread-runnable"); thread.start(); log.debug("main-running"); } }
-
Thread和Runnable联系
-
如果通过Runnable方法创建的线程,实际上是调用的Thread中的run()
-
new Thread()是通过创建子类实现父类Thread的run() 方法
-
Runnable属于组合方式,new Thread属于继承方式 组合优于继承
-
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
-
用 Runnable 更容易与线程池等高级 API 配合
-
用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
方法三:FutureTask
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-07-28 19:48 **/ @Slf4j(topic = "c.FutureTaskDemo") public class FutureTaskDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { log.debug("running-thread"); Thread.sleep(2000); return 100; } }); Thread thread = new Thread(task, "task"); thread.start(); log.debug("{}", task.get()); } }
-
// 输出 Connected to the target VM, address: '127.0.0.1:59755', transport: 'socket' 19:53:51 [task] c.FutureTaskDemo - running-thread 19:53:53 [main] c.FutureTaskDemo - 100 Disconnected from the target VM, address: '127.0.0.1:59755', transport: 'socket' Process finished with exit code 0
3.2 线程运行原理
线程上下文切换
-
因为一下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码
- 线程的CPU时间片用完了
- 垃圾回收(看垃圾回收器,正常时FullGC)
- 有更高优先级的线程需要执行
- 线程自己调用了sleep, yield, wait, join, park, synchronized,lock等方法
-
当Context Switch发生时,需要操作系统保存当前线程的状态(保护现场),并恢复另一个线程的状态,Java中对应的概念就是程序计数器,他的作用是记录下一条JVM指令的执行地址,是线程私有的
-
线程状态包括:程序计数器,虚拟机栈中的每个栈帧的信息,如局部变量,操作数栈,返回地址,动态链接等
-
Context Switch 频繁发生会影响性能
3.3 常见方法
3.3.1 start与run
-
直接调用run() 就是调用的普通方法
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-07-28 20:40 **/ @Slf4j(topic = "c.RunDemo") public class RunDemo { public static void main(String[] args) { Thread thread = new Thread(){ @Override public void run() { log.debug("run"); } }; thread.run(); } }
-
结果
-
20:45:16 [main] c.RunDemo - run // 是mian线程执行的run Process finished with exit code 0
3.3.2 sleep与yield
sleep
-
调用sleep会让当前线程从Running 状态进入到Timed Waiting(有时限的等待)状态
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; /** * @program: ConcurrentStudy * @description: Dmeo * @author: SunYang * @create: 2021-07-28 21:05 **/ @Slf4j(topic = "c.Demo") public class ThreadSleepDemo { public static void main(String[] args) { Thread thread = new Thread("t1"){ @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }; log.debug("{}", thread.getState()); // 21:12:07 [main] c.Demo - NEW thread.start(); log.debug("{}", thread.getState()); // 21:12:07 [main] c.Demo - RUNNABLE try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("{}", thread.getState()); // 21:12:08 [main] c.Demo - TIMED_WAITING } } // 输出 // 21:12:07 [main] c.Demo - NEW // 21:12:07 [main] c.Demo - RUNNABLE // 21:12:08 [main] c.Demo - TIMED_WAITING
-
-
其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-07-28 21:15 **/ @Slf4j(topic = "c.Demo") public class SleepInterruptDemo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("enter sleep"); try { Thread.sleep(2000); } catch (InterruptedException e) { log.debug("wake up ...."); e.printStackTrace(); } }, "t1" ); t1.start(); Thread.sleep(1000); log.debug("interrupt.."); t1.interrupt(); } }
-
输出
-
21:20:41 [t1] c.Demo - enter sleep 21:20:42 [main] c.Demo - interrupt.. 21:20:42 [t1] c.Demo - wake up .... java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.sunyang.concurrentstudy.SleepInterruptDemo.lambda$main$0(SleepInterruptDemo.java:17) at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0
-
-
睡眠结束后的线程未必会立刻得到执行
-
建议用TimeUnit的sleep来代替Thread的sleep来获得更好的可读性
-
TimeUnit.SECONDS.sleep(1);
-
yield
- 调用yeild会让当前线程从Running(运行)转为Runnable(就绪)状态,然后调度其他线程
- 具体的实现依赖于操作系统的任务调度器(如果当前没有别的线程,那么还会继续执行这个线程,或者他刚让出CPU的使用权,立刻就又获得了使用权,那么还会继续执行)
区别
- sleep(阻塞状态)不能获得CPU时间片,分配时间片时不会考虑阻塞状态
- yeild(就绪状态)有机会获得CPU时间片
线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽视它
- 如果CPU比较忙,那么优先级高的线程可能会获得更多的时间片,但CPU闲时,优先级几乎没用
- yeild也是如此。
sleep使用
- 在没有利用CPU来计算时,不要让while(true)空转浪费CPU,这时可以使用yeild和sleep来让出CPU的使用权给其他程序(防止CPU占用100%,单核情况下)
- 可以用wait或条件变量达到类似效果,
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无锁同步的场景
3.3.3 join
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-07-28 22:05 **/ @Slf4j(topic = "c.Demo") public class JoinDemo { static int r = 0; public static void main(String[] args) { Thread thread = new Thread(() -> { log.debug("开始"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } r = 10; log.debug("结束"); }); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("结果{}", r); } }
应用之同步
- 需要等待结果返回,才能继续运行就是i同步
- 不需要等待结果返回,就能继续运行,就是异步
有时效的join
-
如果等够时间还没返回,则继续执行
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @program: ConcurrentStudy * @description: Demo * @author: SunYang * @create: 2021-07-28 22:05 **/ @Slf4j(topic = "c.Demo") public class JoinDemo { static int r = 0; public static void main(String[] args) { Thread thread = new Thread(() -> { log.debug("开始"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } r = 10; log.debug("结束"); }); thread.start(); try { thread.join(500); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("结果{}", r); // 22:17:18 [main] c.Demo - 结果0 } }
-
如果线程提前结束,那么会直接返回结果,不用等待足够的秒数
- thread.join(3000); // 两秒后返回结果了,就继续向下运行,不用等到3秒再继续
3.3.4 interrupt
- 除了可以打断处于阻塞状态的线程,其他正在运行的线程也可以打断
打断阻塞的线程
-
打断sleep,wait,join的线程
-
打断sleep的线程,会清空打断状态,以sleep为例
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @program: ConcurrentStudy * @description: Dmeo * @author: SunYang * @create: 2021-07-28 22:27 **/ @Slf4j(topic = "c.Demo") public class SleepInterruptDemoT { public static void main(String[] args) { Thread thread = new Thread(() -> { log.debug("sleep.."); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } }, "t1"); thread.start(); log.debug("interrupt..."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); log.debug("打断标记被清除--{}", thread.isInterrupted()); } }
-
输出
-
22:31:30 [main] c.Demo - interrupt... 22:31:30 [t1] c.Demo - sleep.. java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.sunyang.concurrentstudy.SleepInterruptDemoT.lambda$main$0(SleepInterruptDemoT.java:19) at java.lang.Thread.run(Thread.java:748) 22:31:31 [main] c.Demo - 打断标记被清除--false Disconnected from the target VM, address: '127.0.0.1:62851', transport: 'socket' Process finished with exit code 0
-
处于Running状态的线程被打断后,不会直接停止运行,而是由被打断线程自己决定,是否停止自己的线程,让出cpu,可以优雅的停止线程,在停止线程之前可以做一些善后工作。
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @program: ConcurrentStudy * @description: Dmeo * @author: SunYang * @create: 2021-07-28 22:27 **/ @Slf4j(topic = "c.Demo") public class SleepInterruptDemoT { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (true){ if (Thread.currentThread().isInterrupted()) { log.debug("被打断了"); break; } log.debug("虽然interrupt我了,但是我还能执行。。"); } }, "t1"); thread.start(); TimeUnit.SECONDS.sleep(1); log.debug("interrupt.."); thread.interrupt(); log.debug("打断状态--{}", thread.isInterrupted()); } }
-
22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [main] c.Demo - interrupt.. 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。 22:44:30 [t1] c.Demo - 被打断了 22:44:30 [main] c.Demo - 打断状态--true
模式之两阶段终止模式
-
使用线程对象的stop()方法停止线程(废弃)
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程就永远无法获取锁吗,就会造成死锁。
-
使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,但这种做法会让整个程序都停止
两阶段终止
-
场景:记录电脑健康状态CPU使用率等等,搞一个后台的监控线程,while(true)循环,每两秒记录一下,并且要有一个打断按钮,不想监控时,可以停下。
-
如果在睡眠状态被打断,会抛出异常,但是打断标记会被清除设置为false,所以要自己手动设置打断标记。
-
package com.sunyang.concurrentstudy; import ch.qos.logback.core.util.TimeUtil; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @Author: sunyang * @Date: 2021/7/29 * @Description: */ @Slf4j(topic = "c.Demo") public class TwoPhaseTerminationDemo { public static void main(String[] args) { TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination(); twoPhaseTermination.start(); try { TimeUnit.SECONDS.sleep(3); twoPhaseTermination.stop(); } catch (InterruptedException e) { e.printStackTrace(); } } } @Slf4j(topic = "c.TwoDemo") class TwoPhaseTermination { private Thread monitor; public void start() { monitor = new Thread(() -> { while (true) { Thread current = Thread.currentThread(); if (current.isInterrupted()) { log.debug("善后工作!"); break; } try { TimeUnit.SECONDS.sleep(1); log.debug("执行监控记录"); } catch (InterruptedException e) { e.printStackTrace(); current.interrupt(); } } }); monitor.start(); } public void stop() { monitor.interrupt(); } }
-
11:02:32 [Thread-0] c.TwoDemo - 执行监控记录 11:02:33 [Thread-0] c.TwoDemo - 执行监控记录 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.sunyang.concurrentstudy.TwoPhaseTermination.lambda$start$0(TwoPhaseTerminationDemo.java:41) at java.lang.Thread.run(Thread.java:748) 11:02:34 [Thread-0] c.TwoDemo - 善后工作!
打断park线程
-
打断park线程,不会清空打断状态(不是Thread中的方法,是LockSupport.park()(锁的支持类中的方法))
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; /** * @Author: sunyang * @Date: 2021/7/29 * @Description: */ @Slf4j(topic = "c.Demo") public class ParkDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { log.debug("park。。。。"); LockSupport.park(); log.debug("unpark..."); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); LockSupport.park(); log.debug("isInterrupted为true情况下执行了park()方法,但是依然继续执行,park()方法未生效"); }, "t1"); thread.start(); TimeUnit.SECONDS.sleep(1); log.debug("thread状态:{}", thread.getState()); thread.interrupt(); } }
-
11:26:58 [t1] c.Demo - park。。。。 11:26:59 [main] c.Demo - thread状态:WAITING 11:26:59 [t1] c.Demo - unpark... 11:26:59 [t1] c.Demo - 打断状态:true 11:26:59 [t1] c.Demo - isInterrupted为true情况下执行了park()方法,但是依然继续执行,park()方法未生效
3.4 主线程和守护线程
-
默认情况下,java进程需要等待所有的线程都运行结束,才会结束,但是有一种特殊的线程,叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @Author: sunyang * @Date: 2021/7/29 * @Description: */ @Slf4j(topic = "c.Demo") public class DaemonThreadDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while(true) { if (Thread.currentThread().isInterrupted()){ break; } } log.debug("结束"); }, "t1"); thread.setDaemon(true); thread.start(); TimeUnit.SECONDS.sleep(1); log.debug("结束"); } }
-
13:38:56 [main] c.Demo - 结束 Process finished with exit code 0
-
垃圾回收器就是一种守护线程
-
Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待他们处理完请求。
3.5 五种状态
-
从操作系统层面来描述
-
【初始状态】
- 仅是在语言层面创建了线程对象,还未与操作系统线程关联
-
【可运行状态】(就绪状态)
- 指该线程已被创建(与操作系统线程关联),可以由CPU调度执行,但还未被cpu分配时间片,还未真正在cpu上运行
-
【运行状态】
-
指获取了CPU时间片,运行中的状态
-
当CPU时间片用完,会从**【运行状态】转换至【可运行状态(就绪状态)】**,会导致线程的上下文切换
-
【阻塞状态】
- 如果调用了阻塞API或者sleep之类的线程方法,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入**【阻塞状态】**
- 等BIO操作完毕,会由操作程序唤醒(应该是中断打断cpu)阻塞的线程,转换至**【可运行状态】**
- 与可运行状态的区别是,对**【阻塞状态】**的线程来说,只要他们一直不被唤醒,调度器就一直不会考虑调度他们,也就是不会考虑为他们分配cpu时间片
-
【终止状态】
- 表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态
3.6 六种状态
- 从Java API 层面来描述
- 根据Thread.State枚举,分为六种状态
API
/**
* 线程状态。 线程可以处于以下状态之一:
NEW 尚未启动的线程处于此状态。
RUNNABLE 在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED 被阻塞等待监视器锁的线程处于这种状态。
WAITING 无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING 等待另一个线程执行操作达指定等待时间的线程处于此状态。
TERMINATED 已退出的线程处于此状态。
一个线程在给定的时间点只能处于一种状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态
*/
public enum State {
/**
* 尚未启动的线程的线程状态
*/
NEW,
/**
* 可运行线程的线程状态。 处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统的其他资源,例如处理器
*/
RUNNABLE,
/**
* 线程阻塞等待监视器锁的线程状态。 处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调用Object.wait后重新进入同步块/方法。
*/
BLOCKED,
/**
*等待线程的线程状态。 由于调用以下方法之一,线程处于等待状态:
Object.wait没有超时
Thread.join没有超时
LockSupport.park
处于等待状态的线程正在等待另一个线程执行特定操作。 例如,在对象上调用Object.wait()的线程正在等待另一个线程在该对象上调用Object.notify()或 Object.notifyAll() 。 调用Thread.join() 的线程正在等待指定的线程终止。
*/
WAITING,
/**
* 具有指定等待时间的等待线程的线程状态。 由于使用指定的正等待时间调用以下方法之一,线程处于定时等待状态:
Thread.sleep
Object.wait超时
Thread.join超时
LockSupport.parkNanos
LockSupport.parkUntil
*/
TIMED_WAITING,
/**
* 终止线程的线程状态。 线程已完成执行
*/
TERMINATED;
}
-
初始,可运行,阻塞,等待,超时等待,终止
-
【NEW】
- 线程刚被创建,但是还没有调用start()方法
-
【RUNNABLE】
- 当调用了start()方法之后,注意,Java API 层面的RUNNABLE状态涵盖了操作系统层面的**【可运行状态】,【运行状态】和【阻塞状态】**
- 由于BIO导致的线程阻塞,再JAVA里无法区分,仍然认为是可运行
- **【BLOCKED】,【WAITING】, 【TIMED_WAITING】都是Java API 层面对【阻塞状态】**的细分。
-
【TERMINATED】
-
当线程代码结束运行
-
【BLOCKED】
-
【WAITING】
- join()
-
【TIMED_WAITING】
-
sleep
-
package com.sunyang.concurrentstudy; import lombok.extern.slf4j.Slf4j; import java.sql.Time; import java.util.concurrent.TimeUnit; /** * @Author: sunyang * @Date: 2021/7/29 * @Description: */ @Slf4j(topic = "c.Demo") public class ThreadSixStateDemo { public static void main(String[] args) { Thread t1 = new Thread(() -> { log.debug("running....."); // NEW }, "t1"); Thread t2 = new Thread(() -> { while (true){ } }); t2.start(); // Runnable Thread t3 = new Thread(() -> { log.debug("running....."); }); t3.start(); // TERMINATED Thread t4 = new Thread(() -> { synchronized (ThreadSixStateDemo.class){ try { TimeUnit.SECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t4.start(); // TIMED_WAITING Thread t5 = new Thread(() -> { try { t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); t5.start(); // WAITING Thread t6 = new Thread(() -> { synchronized (ThreadSixStateDemo.class){ try { TimeUnit.SECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t6.start(); // BLOCKED try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("t1 state {}", t1.getState()); log.debug("t2 state {}", t2.getState()); log.debug("t3 state {}", t3.getState()); log.debug("t4 state {}", t4.getState()); log.debug("t5 state {}", t5.getState()); log.debug("t6 state {}", t6.getState()); } }
-
16:56:11 [Thread-1] c.Demo - running..... 16:56:12 [main] c.Demo - t1 state NEW 16:56:12 [main] c.Demo - t2 state RUNNABLE 16:56:12 [main] c.Demo - t3 state TERMINATED 16:56:12 [main] c.Demo - t4 state TIMED_WAITING 16:56:12 [main] c.Demo - t5 state WAITING 16:56:12 [main] c.Demo - t6 state BLOCKED