JUC并发编程学之基础知识
一、基本概念
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。
2.2 并行
多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。
2.3 例子
- 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发。
- 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)
- 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行。
2.4 结论
- 在单核CPU下,多线程不能实际提高程序运行的效率,因为在微观下CPU还是会根据时间片分配线程,也就等同于单线程运行,此外CPU轮询线程还会因为上下文切换造成比不用多线程更多的时间消耗。
- 在多核CPU下,多线程可以提高程序运行的效率,每个CPU执行各自线程的代码,花费时间取决于最长的那个线程的运行时间,加上很短的汇总时间。
二、Java线程
1. 创建和运行线程
注意点:一个线程调用两次start()会抛出IllegalThreadStateException,这是一种运行时异常。
1.1 方法一:直接使用Thread
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
1.2 方法二:使用Runnable接口
把【线程】和【任务】(要执行的代码)分开
Thread 代表线程
Runnable 可运行的任务(线程要执行的代码)
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
点击Runnable接口,可以看到上面有个@FunctionalInterface注解,标注该注解的接口只能拥有一个抽象方法,此外使用此注解可以通过lambda表达式创建该接口的实现类并默认实现该方法。所以可以简化成以下代码创建Runnable的实现类。
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
通过Runnable创建Thread时的源码解析,可以看到最终会将Runnable的实现类赋值给Thread的成员变量target。
在调用Thread类的run()方法时,若该方法没有被重写也就是使用线程和任务分开的形式创建线程时会发现直接调用了成员变量target的run()方法,也就是初始化时传递过来的Runnable接口的实现类。
1.3 方法三:FutureTask 配合Thread
FutureTask的get()方法是一个阻塞方法,若一个线程需要获取另一个线程的值,在通过get()方法获取到值之前,该线程处于阻塞状态,即以下的代码不能执行,只有获取到值以后代码才能继续执行。
// 创建任务对象
FutureTask<Integer> task2 = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("hello");
return 100;
}
});
//还可以这样创建
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
Thread.sleep(2000);
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
执行结果,可以看到主线程阻塞了2秒之后才执行:
20:38:43.164 c.Test2 [t3] - hello
20:38:45.166 c.Test2 [main] - 结果是:100
FutureTask是一个实现了Runnable接口的类,它有一个传递的是一个Callable接口的构造方法。Callable接口和Runnable接口一样,都可以用函数式声明的方式创建。
因为实现了Runnable接口,所以同上面所述,在调用thread的run()方法时会默认调用FutureTask对象的run()方法,可以看以下代码,在调用FutureTask对象的run()方法时会直接调用成员变量Callable接口实现类的run()方法,也就是我们重写后的run()方法,得到返回值以后将返回值赋值给了成员变量outcome,调用get()方法时会调用report()方法获取outcome的值。
2. 查看进程线程的方法
-
windows
任务管理器可以查看进程和线程数,也可以用来杀死进程
tasklist | findstr java 查看java的进程
netstat -ano | findstr 查找指定端口的进程
taskkill /F /PID 杀死进程
-
linux
ps -fe 查看所有进程
ps -fT -p 查看某个进程(PID)的所有线程
kill 杀死进程
top 按大写 H 切换是否显示线程
top -H -p 查看某个进程(PID)的所有线程 -
Java
jps 命令查看所有 Java 进程
jstack 查看某个 Java 进程(PID)的所有线程状态
jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
3. 使用jconsole远程监控
jconsole是jdk自带的编译工具,位于java的bin目录下,负责监控java进程和线程的运行,除了可以监控本地java进程外,还可远程监控其他主机的java进程,以下是远程监控的配置,需在被监控的主机上配置,配置后,别的主机可以通过配置的ip地址和端口号远程监控此主机的java进程。
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
Dcom.sun.management.jmxremote.authenticate=是否认证 java类
4. 原理之线程运行
4.1 栈与栈帧
public class TestFrames {
public static void main(String[] args) {
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
4.2 线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
- 线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念
就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
Context Switch 频繁发生会影响性能
5. 线程常用方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线程,在新的线程运行 run 方法中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException | |
run() | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为 | |
join() | 等待线程运行结束才能继续运行 | ||
join(long n) | 等待线程运行结束,最多等待 n 毫秒 | ||
getId() | 获取线程长整型的 id | id 唯一 | |
getName() | 获取线程名 | ||
setName(String) | 修改线程名 | ||
getPriority() | 获取线程优先级 | 有三种默认取值:MIN_PRIORITY(1),NORM_PRIORITY(5) ,MAX_PRIORITY(10) | |
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU调度的机率 | |
getState() | 获取线程状态 | Java中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
isInterrupted() | 判断是否被打断 | 默认为true,当处于阻塞状态时被打断会置成false,不会清除打断标记 | |
isAlive() | 线程是否存活(还没有运行完毕) | ||
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置打断标记为true ;park 的线程被打断,也会设置打断标记为true | |
interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程 | |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
6. start与run
调用start()方法后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于就绪状态,一旦该线程被任务调度器分到时间片,就会通过JVM调用该线程的run()方法。
倘若在主线程里直接调用另一个线程的run()方法,就会被当成是一个普通方法,两个线程就不会同时运行。
7. sleep与yield
sleep
- 调用 sleep 会让当前线程从 Running(运行) 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行,取决于任务调度器
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程,但是如果没有其他线程执行,任务调度器还是会把时间片分配给该线程然后执行。
- 具体的实现依赖于操作系统的任务调度器。
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("t1 state: {}", t1.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state: {}", t1.getState());
}
}
执行结果
22:09:56.869 c.Test6 [main] - t1 state: RUNNABLE #主线程先运行,检测到t1是就绪状态,所以是runnable
22:09:57.373 c.Test6 [main] - t1 state: TIMED_WAITING #主线程检测到t1线程处于睡眠状态
interrupt方法,打断一个线程并抛出异常
@Slf4j(topic = "c.Test7")
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("enter sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
}
}
执行结果:
22:17:18.093 c.Test7 [t1] - enter sleep...
22:17:19.093 c.Test7 [main] - interrupt...
22:17:19.093 c.Test7 [t1] - wake up...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.itcast.test.Test7$1.run(Test7.java:14)
线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
8. join 方法详解
join()方法可以看做是一个阻塞方法,类似于FutureTask的get()方法,只有等使用该方法的线程执行完成才能继续执行。
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
执行结果:
22:46:19.764 c.Test10 [main] - 开始
22:46:19.802 c.Test10 [t1] - 开始
22:46:20.814 c.Test10 [t1] - 结束
22:46:20.814 c.Test10 [main] - 结果为:10
22:46:20.816 c.Test10 [main] - 结束
Process finished with exit code 0
以下代码测试多个线程阻塞的情况最终耗时为多少,注意此代码运行在多核环境下,若运行在单核环境下最终耗时应该为多少,个人认为应该还是2秒,因为沉睡1秒的线程调用sleep方法后会进入睡眠状态1秒,在此之间任务调度器不会再分配时间片给该线程,同时沉睡2秒的线程调用sleep方法后会进入睡眠状态2秒,在此之间任务调度器不会再分配时间片给该线程,所以此时主线程是可以运行的,调用了t2.join()方法后需等待2秒后主线程才能执行,在此期间t1的1秒睡眠时间已经结束,待t2线程睡醒后主线程执行t1.join()方法不会出现阻塞状态,所以最终的时间为2秒。
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r = 0;
static int r1 = 0;
static int r2 = 0;
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
t2.join();
log.debug("t2 join end");
t1.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
执行结果:可以看到最后的结果为2秒,在多个线程同时启动的情况下,阻塞时间取决于阻塞时间最长的那个线程
22:55:52.401 c.TestJoin [main] - join begin
22:55:54.400 c.TestJoin [main] - t2 join end
22:55:54.400 c.TestJoin [main] - t1 join end
22:55:54.400 c.TestJoin [main] - r1: 10 r2: 20 cost: 2000
join (等待时间)设置等待时间,时间内线程阻塞,超过时间线程不再阻塞。
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r = 0;
static int r1 = 0;
static int r2 = 0;
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(2);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
log.debug("join begin");
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
执行结果:可以看到main线程只等待了1.5秒就不阻塞了,此外若将等待时间设置为3秒,它最后阻塞的时间还是为2秒。
23:13:15.748 c.TestJoin [main] - join begin
23:13:17.251 c.TestJoin [main] - r1: 0 r2: 0 cost: 1505
9. interrupt 方法详解
9.1 interrupt 的一个简单案例
thread.isInterruptet()的默认值为false,打断处于sleep,wait,join 的线程, 会清空打断状态即将打断标记设置为false,同时抛出InterruptedException,以 sleep 为例:
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000); // wait, join
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
}
执行结果:只有一个线程处于阻塞状态(调用了sleep join wait) 方法时将线程打断,调用其isInterrupt()方法才会得到false
23:34:56.594 c.Test11 [t1] - sleep...
23:34:57.592 c.Test11 [main] - interrupt
23:34:57.592 c.Test11 [main] - 打断标记:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.itcast.test.Test11.lambda$main$0(Test11.java:12)
9.2 使用interrupt优雅地停止一个线程
在线程内部添加一个while()循环,不断地判断它的打断标记是否为真,当为真时结束run()方法,即停止线程。是否停止该线程取决于外部,通过interrupt()会将处于正常运行状态线程的打断状态设置为true,这样我们可以自由地控制此线程的停止,停止的同时还可做额外的事。
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();//在被打断之前一直为false
if(interrupted) {
log.debug("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
执行结果:
20:55:55.349 c.Test12 [main] - interrupt
20:55:55.351 c.Test12 [t1] - 被打断了, 退出循环
9.3 使用interrupt编写一个简单的监控程序
我们不需要一直循环判断线程的打断标记,因为这样会浪费CPU资源。在if条件不满足的情况让线程休眠1秒,大多数情况下线程会在处于休眠状态的时候被打断,此时就会抛出异常转区执行catch()代码块中的代码,在里面将打断标记置为true。
@Slf4j(topic = "c.TestInterrupt")
public class TestInterrupt {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination1 tpt = new TwoPhaseTermination1();
tpt.start();//启动监控程序
Thread.sleep(5000);
tpt.start();//关闭监控程序
}
}
/**
* 模拟一个监控程序
*/
@Slf4j(topic = "c.TwoPhaseTermination1")
class TwoPhaseTermination1{
private Thread monitor;
public void start(){
monitor = new Thread(() ->{
while (true){
Thread current = Thread.currentThread();
if (current.isInterrupted()){
log.debug("料理后事");
break;
}else {
try {
Thread.sleep(1000);//情况一:处于sleep状态下的线程被打断后会将isInterrupted置为false同时抛出异常
log.debug("执行监控记录");//情况二:线程正常运行情况下被打断会将isInterrupted置为true
} catch (InterruptedException e) {
e.printStackTrace();
//将打断标记置为true
current.interrupt();
}
}
}
});
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
执行结果:
21:26:27.939 c.TwoPhaseTermination1 [Thread-0] - 执行监控记录
21:26:28.941 c.TwoPhaseTermination1 [Thread-0] - 执行监控记录
21:26:29.941 c.TwoPhaseTermination1 [Thread-0] - 执行监控记录
21:26:30.941 c.TwoPhaseTermination1 [Thread-0] - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.itcast.test.TwoPhaseTermination1.lambda$start$0(TestInterrupt.java:32)
at java.lang.Thread.run(Thread.java:748)
21:26:31.940 c.TwoPhaseTermination1 [Thread-0] - 料理后事
9.4 打断park线程
LockSupport.park()方法让线程进入WAITTING状态,即后续代码不会执行,但如果这时线程被打断会将打断标记置为true,类似于正常运行时被打断。LockSupport.park()只适用于线程打断标记为false的情况下,若打断标记为true不会让线程进入WAITTING状态。
可以使用Thread.interrupt()方法将线程打断的同时将打断标记设置为false。
@Slf4j(topic = "c.Test14")
public class Test14 {
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.interrupted());//获取打断标记后将打断标记设置为false
}, "t1");
t1.start();
sleep(1);
t1.interrupt();
}
public static void main(String[] args) throws InterruptedException {
test3();
}
}
执行结果:
21:48:12.583 c.Test14 [t1] - park...
21:48:13.583 c.Test14 [t1] - unpark...
21:48:13.583 c.Test14 [t1] - 打断状态:true
9.5 不推荐的方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
9.6 主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
log.debug("结束");
}, "t1");
t1.setDaemon(true);//将t1线程设置为守护线程
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
}
10. 线程的状态
10.1 操作系统层面的线程状态
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联。
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行。
- 【运行状态】指获取了 CPU 时间片运行中的状态。当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换。
- 【阻塞状态】如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】,等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】。
与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑
调度它们。 - 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态。
10.2 Java层面的线程状态
-
【NEW】:创建了线程对象但还没有启动该线程,此时调用getState()就会到的NEW。
-
【RUNNABLE】:有以下三种情况
i. 可运行状态:表示该线程已经被分配到了时间片但还没有开始执行
ii. 运行状态:表示线程正在运行代码中
iii. 阻塞状态:由于BIO导致的线程阻塞在Java 里无法区分,仍然认为是可运行 -
【BLOCKED】:当一个线程等待另一个线程释放锁时
-
【WAITING】:当一个线程调用了join()方法后
-
【TIMED_WAITING】:当一个线程调用了sleep()、wait()方法后就会处TIMED_WAITNG状态,或调用了join(long time)方法。
-
【TERMINATED】:线程的run()方法执行完毕。
线程状态演示实例
@Slf4j(topic = "c.TestState")
public class TestState {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread("t1") {//NEW
@Override
public void run() {
log.debug("running...");
}
};
Thread t2 = new Thread("t2") {// RUNNABLE
@Override
public void run() {
while(true) { // runnable
}
}
};
t2.start();
Thread t3 = new Thread("t3") {//TERMINATED
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
Thread t4 = new Thread("t4") {//TIMED_WAITING
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
Thread t5 = new Thread("t5") {//WAITING
@Override
public void run() {
try {
t2.join(); // waiting 若调用的是t2.join(long)则是TIME_WAITING状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6") {//BLOCKED
@Override
public void run() {
synchronized (TestState.class) { // blocked
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} 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());
System.in.read();
}
}
执行结果:
22:44:06.999 c.TestState [t3] - running...
22:44:07.498 c.TestState [main] - t1 state NEW
22:44:07.499 c.TestState [main] - t2 state RUNNABLE
22:44:07.499 c.TestState [main] - t3 state TERMINATED
22:44:07.499 c.TestState [main] - t4 state TIMED_WAITING
22:44:07.499 c.TestState [main] - t5 state WAITING
22:44:07.499 c.TestState [main] - t6 state BLOCKED