Java并发编程(一) 线程介绍、常用方法、两阶段终止状态、线程的五种六种状态
1. 线程与进程
1.1 进程与进程
进程: 资源分配的最小单位
- 进程是线程的容器, 一个进程中包含多个线程, 真正执行任务的是线程
线程: 资源调度的最小单位
进程
程序由指令和数据组成,但是这些 指令要运行,数据要读写,就必须将指令加载到cpu,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备,进程就是用来加载指令,管理内存,管理IO的- 当一个指令被运行,从磁盘加载这个程序的代码到内存,这时候就开启了一个进程,进程是系统进行资源分配和调度的基本单位。
进程就可以视为程序的一个实例,大部分程序都可以运行多个实例进程(例如记事本,浏览器等),部分只可以运行一个实例进程(例如360安全卫士)
线程
- 一个进程之内可以分为
多个线程。 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行,所以真正占用CPU运行的是线程- Java 中,线程作为资源的最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器。
二者对比
- 进程基本上
相互独立的,而线程存在于进程内,是进程的一个子集 - 进程拥有共享的资源,如内存空间等,供其内部的线程共享; 进程间通信较为复杂 同一台计算机的进程通信称为 IPC(Inter-process communication)
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP- 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更
轻量,线程上下文切换成本一般上要比进程上下文切换低
1.2 并行与并发
并发: 在单核CPU下, 一定是
并发执行的, 也就是在同一个时间段内一起执行. 实际还是串行执行, CPU的时间片切换非常快, 给人一种同时运行的感觉。
并行: 在多核CPU下, 能真正意义上实现
并行执行, 在同一个时刻, 多个线程同时执行; 比如说2核cpu, 同时执行4个线程. 理论上同时可以有2个线程是并行执行的. 此时还是存在并发, 因为2个cpu也会同时切换不同的线程执行任务罢了
并发 (concurrent)
微观串行, 宏观并行- 在
单核 cpu下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu 在线程间(时间片很短)的切换非常快,给人的 感觉是同时运行的 。一般会将这种线程轮流使用 CPU的做法称为并发(concurrent) - 将
线程轮流使用cput称为并发(concurrent)
并行
二者对比
- 引用 Rob Pike 的一段描述:
并发(concurrent): 是同一时间应对(dealing with)多件事情的能力并行(parallel): 是同一时间动手做(doing)多件事情的能力
例子
- 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是
并发 - 家庭主妇雇了个保姆,她们一起这些事,这时
既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待) - 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是
并行
1.3 应用
应用1:异步调用
同步与异步:以调用方的角度讲
- 如果
需要等待结果返回才能继续运行的话就是同步 - 如果
不需要等待就是异步
设计:
- 多线程可以让方法执行变为
异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这5秒cpu什么都做不了,其它代码都得暂停
结论:
- 比如在项目中,视频文件需要转换格式等操作比较
费时,这时开一个新线程处理视频转换,避免阻塞主线程 - tomcat 的异步 servlet 也是类似的目的,
让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程 - UI 程序中,开线程进行其他操作,避免阻塞 UI 线程
应用2:提高效率
充分利用多核 cpu 的优势,提高运行效率。想象下面的场景,执行 3 个计算,最后将计算结果汇总。
计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
- 如果是串行执行,那么总共花费的时间是 10 + 11 + 9 + 1 = 31ms
- 但如果是四核 cpu,各个核心分别使用线程 1 执行计算 1,线程 2 执行计算 2,线程 3 执行计算 3,那么 3 个 线程是并行的,花费时间只取决于最长的那个线程运行的时间,即 11ms 最后加上汇总时间只会花费 12ms
结论:
- 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用 cpu ,不至于一个线程总占用 cpu,别的线程没法干活
- 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的
- 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任 务都能拆分(参考后文的【阿姆达尔定律】)
- 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
- IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一 直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化
2. Java线程
2.1 创建和运行线程(重要)
方法一:直接使用Thread
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {//使用匿名内部类
@Override
// run 方法内实现了要执行的任务
public void run() {
log.debug("hello");
}
};
//启动线程
t1.start();
方法二:使用Runnable配合Thread (推荐)
把【线程】和【任务】(要执行的代码)分开
Thread:代表线程Runnable:可运行的任务(线程要执行的代码)
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
- 当一个接口带有
@FunctionalInterface注解时,表示是一个函数式接口,因此Runable接口是可以使用lambda来简化操作的 - 所以方法二中的代码可以被简化为
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
* 原理之 Thread 与 Runnable 的关系
- 分析
Thread的源码,理清它与Runnable的关系
小结
- 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
- **用 Runnable 更容易与线程池等高级 API 配合 **
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
方法三:使用FutureTask与Thread结合
使用FutureTask可以接收 Callable 类型的参数,用来处理有返回结果的情况(Runnable的run方法没有返回值)
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t3 = new Thread(task3, "t3");
t3.start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
方法四:使用线程池来创建线程
/**
* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
*
* 面试题:创建多线程有几种方式?四种!
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合使用于Runnable
service.execute(new NumberThread1());//适合使用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
总结
- 使用 继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果
使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以 - 开发中一般使用
线程池的方式
2.2 查看进程线程的方法
2.3 线程运行原理 (重点)
虚拟机栈与栈帧
虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程私有的。当Java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧(在栈顶),对应着当前正在执行的那个方法
例子:
public class TestFrames {
public static void main(String[] args) {
method1(10);
}
public static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
public static void method2() {
Object n = new Object();
return n;
}
}
该图展示的是单个线程的运行时数据区的状态,当有多线程时,由于虚拟机栈是线程私有的,因此每个线程都会维护自己的虚拟机栈,每个线程调用一个方法时,就会创建一个对应于该方法的栈帧。
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程
- 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了
sleep、yield、wait、join、park、synchronized、lock等方法
当Thread Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等- Context Switch 频繁发生会
影响性能
2.4 Thread的常见方法
2.5 调用start 与 run方法的区别
调用start()方法
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
log.debug("我是一个新建的线程正在运行中");
FileReader.read(fileName);
}
};
thread.setName("新建线程");
thread.start();
log.debug("主线程");
}
- 输出:程序在
t1 线程运行,run()方法里面内容的调用是异步的代码
11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程
11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start ...
11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms
调用run()方法
- 将上面代码的
thread.start();改为thread.run();输出结果如下:程序仍在 main 线程运行,run()方法里面内容的调用还是同步的
12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...
12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms
12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - 主线程
小结
- 直接调用
run()是在主线程中执行了run(),没有启动新的线程 - 使用
start()是启动新的线程,通过新的线程间接执行run()方法中的代码
2.6 sleep 与 yield
sleep方法
-
调用
sleep()会让当前线程从Running(运行状态)进入Timed Waiting 状态(阻塞) -
其它线程可以使用
interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】 -
睡眠结束后的线程未必会立刻得到执行 (需要分配到cpu时间片)
-
建议用
TimeUnit的sleep()代替 Thread 的sleep()来获得更好的可读性//可以控制睡眠时间单位,可读性更好 TimeUnit.SECONDS.sleep(1) //sleep的时间单位默认为毫秒ms Thread.sleep(1000)
yield方法
- 调用 yield 会让当前线程从
Running进入Runnable 就绪状态,然后调度执行其它线程 - 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法,但是也没有用)
两者对比
- yield使cpu调用其它线程,
但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
线程优先级
- 线程
优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高。最大为10,最小为1,默认为5
2.7 join方法详解
为什么要使用join
- 在
主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行
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();
// 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
// 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
log.debug("结果为:{}", r);
log.debug("结束");
}
应用之同步
以调用方角度来讲,如果:
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
下图, 因为开辟了t1线程. 此时程序中有两个线程; main线程和t1线程; 此时在main线程中调用
t1.join, 所以main线程只能阻塞等待t1线程执行完.t1线程在1s后将r=10, t1线程执行完, 此时main线程才会接着执行![]()
问,下面代码 cost 大约多少秒?
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
分析如下
- 第一个
join:等待 t1 时,主线程停止但是 t2 并没有停止,而在运行,因此 t2 sleep了1秒 - 第二个
join:1s 后,执行到此,t2 也已经运行了 1s,因此也只需再等待 1s
如果颠倒两个 join 呢?最终也是只进行了2s。输出:
20:45:43.239 [main] c.TestJoin - r1: 10 r2: 20 cost: 2005
有时效的 join
等够时间
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出:
20:48:01.320 [main] c.TestJoin - r1: 10 r2: 0 cost: 1010
没等够时间:
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(2);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 超过时间之后会导致 join 结束,此时线程还处于 sleep 状态
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出:
20:52:15.623 [main] c.TestJoin - r1: 0 r2: 0 cost: 1502
2.8 interrupt 方法详解
打断 sleep,wait,join
该方法用于打断 sleep,wait,join的线程, 在阻塞期间cpu不会分配给时间片
- 先了解一些
interrupt()方法的相关知识:博客地址 - 如果
一个线程在在运行中被打断,打断标记会被置为true - 如果是打断
因sleep wait join方法而被阻塞的线程,会将打断标记置为false
sleep,wait,join的线程,这几个方法都会让线程进入阻塞状态,以 sleep 为例
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("sleep...");
try {
Thread.sleep(5000); // wait, join
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
Thread.sleep(1000);
System.out.println("iterrupt..");
t1.interrupt();
System.out.println(t1.isInterrupted()); // 如果是打断sleep,wait,join的线程, 即使打断了, 标记也为false
}
}
输出:
sleep...
iterrupt..
打断标记为:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.guizy.ThreadPrintDemo.lambda$main$0(ThreadPrintDemo.java:14)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
打断正常运行的线程
- 打断正常运行的线程, 线程并不会暂停,只是调用方法
Thread.currentThread().isInterrupted();的返回值为true,可以判断Thread.currentThread().isInterrupted();的值来手动停止线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted) {
System.out.println("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
System.out.println("interrupt");
t1.interrupt();
System.out.println("打断标记为: "+t1.isInterrupted());
}
interrupt
被打断了, 退出循环
打断标记为: true
Process finished with exit code 0
终止模式之两阶段终止模式
当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。
- Two Phase Termination,就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2线程一个处理其他事情的机会(如释放锁)。
错误思路:
-
使用线程对象的
stop()方法停止线程- stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁
-
使用
System.exit(int)方法停止线程- 目的仅是停止一个线程,但是这种做法会让整个程序都停止
-
如下所示:那么线程的
isInterrupted()方法可以取得线程的打断标记- 如果线程在睡眠
sleep期间被打断,打断标记是不会变的,为false,但是sleep期间被打断会抛出异常,我们据此手动设置打断标记为true; - 如果是在
程序正常运行期间被打断的,那么打断标记就被自动设置为true。处理好这两种情况那我们就可以放心地来料理后事啦!
- 如果线程在睡眠
代码实现如下:
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Monitor monitor = new Monitor();
monitor.start();
Thread.sleep(3500);
monitor.stop();
}
}
class Monitor {
Thread monitor;
/**
* 启动监控器线程
*/
public void start() {
//设置监控器线程,用于监控线程状态
monitor = new Thread() {
@Override
public void run() {
//开始不停的监控
while (true) {
//判断当前线程是否被打断了
if(Thread.currentThread().isInterrupted()) {
System.out.println("处理后续任务");
//终止线程执行
break;
}
System.out.println("监控器运行中...");
try {
//线程休眠
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
Thread.currentThread().interrupt();
}
}
}
};
monitor.start();
}
/**
* 用于停止监控器线程
*/
public void stop() {
//打断线程
monitor.interrupt();
}
}
打断 park 线程
打断 park 线程, 不会清空打断状态
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();//让当前线程停下来
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
sleep(0.5);
t1.interrupt();
}
输出:
21:11:52.795 [t1] c.TestInterrupt - park...
21:11:53.295 [t1] c.TestInterrupt - unpark...
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
private static void test4() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}
});
t1.start();
sleep(1);
t1.interrupt();
}
输出
21:13:48.783 [Thread-0] c.TestInterrupt - park...
21:13:49.809 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.812 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
提示 可以使用
Thread.interrupted()清除打断状态
2.9 sleep,yiled,wait,join 对比及其他不推荐方法
补充:
- sleep,join,yield,interrupted是Thread类中的方法
- wait/notify是object中的方法
- sleep 不释放锁、释放cpu
- join 释放锁、抢占cpu
- yiled 不释放锁、释放cpu
- wait 释放锁、释放cpu
2.10 守护线程
- 默认情况下,当
Java进程中有多个线程在执行时,只有当所有线程都执行完毕后,Java进程才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程结束了,即使守护线程没有执行完毕,也会强制结束。
例:
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
输出
08:26:38.123 [main] c.TestDaemon - 开始运行...
08:26:38.213 [daemon] c.TestDaemon - 开始运行...
08:26:39.215 [main] c.TestDaemon - 运行结束...
注意:
垃圾回收器线程就是一种守护线程- Tomcat 中的
Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
2.11 五种状态
-
初始状态,仅仅是在语言层面上创建了线程对象,即Thead thread = new Thead();,还未与操作系统线程关联 -
可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行 -
运行状态,指线程获取了CPU时间片,正在运行- 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
-
阻塞状态- 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
- 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
-
终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
2.12 六种状态
- 这是从 Java API 层面来描述的
- 根据
Thread.State 枚举,分为六种状态
新建状态、运行状态(就绪状态, 运行中状态)、阻塞状态、等待状态、定时等待状态、终止状态
NEW (新建状态)线程刚被创建,但是还没有调用 start() 方法RUNNABLE (运行状态)当调用了start() 方法之后,注意,Java API 层面的RUNNABLE 状态涵盖了操作系统层面的 【就绪状态】、【运行中状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行)BLOCKED (阻塞状态),WAITING (等待状态),TIMED_WAITING(定时等待状态)都是 Java API 层面对【阻塞状态】的细分,如 sleep 就为 TIMED_WAITING, **join **为 **WAITING **状态。后面会在状态转换一节详述。TERMINATED (结束状态)当线程代码运行结束
@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") {
@Override
public void run() {
while(true) { // runnable 状态
}
}
};
t2.start();
Thread t3 = new Thread("t3") {
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
Thread t4 = new Thread("t4") {
@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") {
@Override
public void run() {
try {
t2.join(); // waiting 状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6") {
@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());
}
}
2.13 本章小结
本章的重点在于掌握
- 线程创建
- 线程重要 api,如 start,run,sleep,join,interrupt 等
- 线程状态
应用方面
- 异步调用:主线程执行期间,其它线程异步执行耗时操作
- 提高效率:并行计算,缩短运算时间
- 同步等待:join
- 统筹规划:合理使用线程,得到最优效果
原理方面
- 线程运行流程:栈、栈帧、上下文切换、程序计数器
- Thread 两种创建方式的源码
- 模式方面
- 终止模式之两阶段终止
- 原文作者:jchen
- 原文链接:http://jchenTech.github.io/post/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%B9%B6%E5%8F%91/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%80-%E7%BA%BF%E7%A8%8B%E4%BB%8B%E7%BB%8D%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%E4%B8%A4%E9%98%B6%E6%AE%B5%E7%BB%88%E6%AD%A2%E7%8A%B6%E6%80%81%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BA%94%E7%A7%8D%E5%85%AD%E7%A7%8D%E7%8A%B6%E6%80%81/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。