没有强大理论支撑的代码,都是自娱自乐,架构师带你学习多线程的原理到实战项目中的高并发解决方案,闲暇之余,分享技术干货,和喜爱技术的coder们一起交流,互相学习进步
相对于计算机而言,每一个任务就是一个进程,每一个进程至少有一个线程,线程可以理解为轻量级进程,计算机操作系统一般不止一个线程运行,当启动了一个JVM时,就会创建一个进程,JVM进程会派生或创建多个线程,多个进程或线程可以并行执行
每一个线程都有自己的局部变量表、程序计数器以及生命周期等,理解线程的生命周期,在日常的开发工作中会避免很多坑,线程生命周期分为5个阶段:NEW、RUNNABLE、RUNNING、BLOCKED、TERMINATED,下面对线程的五种状态详细说明
1. 线程的NEW状态(新建)
创建一个Thread对象时,线程进入NEW状态,准确的讲,此时并没有在JVM中创建线程,与普通的java对象没有区别,需要通过调用start方法,使线程进入RUNNABLE状态
2. 线程的RUNNABLE状态(就绪)
Thread线程对象调用start方法,会在JVM中创建一个线程,此时线程进入RUNNABLE状态,RUNNABLE是中间状态,只是获得了线程执行的资格,需要获取CPU调度执行权才会进入RUNNING状态,RUNNABLE状态只能意外终止或进入RUNNING状态
3. 线程的RUNNING状态(运行)
当线程获得了CPU调度执行权,线程进入RUNNING状态,此时才会执行线程中的业务逻辑,RUNNING状态的线程可以发生以下状态转换:
(1) 调用sleep,或者wait方法添加到waitSet中,进入BLOCKED状态
(2) IO阻塞,进入BLOCKED状态
(3) 获取锁,加入到阻塞队列,进入BLOCKED状态
(4) 由于CPU调度器轮询,使该线程放弃执行,进入RUNNABEL状态
(5) 线程主动调用yield方法,放弃线程执行,进入RUNNABEL状态
(6) 调用JDK不推荐的stop方法或者通过判断标识,进入TERNIMATED状态
4. 线程的BLOCKED状态(阻塞)
线程进入阻塞状态的原因:IO阻塞、获取锁进入阻塞队列、调用sleep方法、wait方法加入到waitSet,BLOCKED状态的线程可以发生以下状态转换:
(1) IO阻塞完成,进入RUNNABLE状态
(2) 线程在阻塞过程中,其他线程调用了interrupt方法,导致线程被打断,进入RUNNABLE状态
(3) 线程调用的sleep休眠时间完成,进入RUNNABLE状态
(4) 调查调用wait方法后,通过notify或notifyAll方法唤醒现场,进入RUNNABLE状态
(5) 获取资源锁,进入RUNNABLE状态
(6) 调用JDK不推荐的stop方法或者通过判断标识,进入TERNIMATED状态
5. 线程的TERMINATED状态(销毁)
TERMINATED状态是线程的最终状态,线程进入TERMINATED状态,意味着线程生命周期结束,不会转换为其他状态,线程进入TERMINATED的条件:
(1) 线程正常执行完成,线程结束
(2) 线程运行出错意外结束
(3) JVM Crash导致所有线程结束
private volatile int threadStatus = 0;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
@Override
public void run() {
if (target != null) {
target.run();
}
}
结合以上源码,总结一下:
(1) 创建Thread对象,threadStatus为线程的状态标识,初始状态为0,该状态在JVM中修改,因为start0是native修饰的,所以start0的实现是C++方法,所以有一点做得非常友好,threadStatus是volatile修饰的内存可见,java可以获取threadStatus的最新状态
(2) 线程调用start方法后,线程状态切换,threadStatus会修改,再次调用start方法,threadStatus不等于0,会抛
IllegalThreadStateException异常
(3) 这里讲一下针对不同线程状态的两个测试用例,就不附带代码了,自己多敲敲
(1) 创建一个Thread类,重写run方法,方法中休眠5秒,重复调用start方法,本次测试线程运行过程重复启动
(2) 创建一个Thread类,重写run方法,方法中休眠5秒,调用start方法,调用sleep方法,休眠10秒,再调用一次start方法,本次测试线程生命周期结束后,再次启动
创建线程的方式
以前我们面试经常会被问到,创建线程的方式有哪些?通常会回答,继承Thread类、实现Runnable接口,这种说法是不严谨的,准确的说,创建线程的方式只有一种,就是构造Thread类,实现线程执行单元的方式有2种:
(1) 重写Thread类的run方法
(2) 实现Runnable接口的run方法,并且将Runnable作为构造Thread类的参数
以上两种方式的不同点在于,重写Thread类的run方法,多个Thread的run方法是独立的,不能作为共享单元,而同一个Runnable实例,可以构造不同的Thread,也就是说通过实现Runnable接口可以接口共享资源的问题(线程安全问题暂时不考虑,后面会讲解)
设计模式应用
1. 模板设计模式
Thread中的start方法和run方法应用了模板设计模式,父类定义算法结构,行为由父类控制,不允许被重写,子类实现需要的逻辑细节
2. 策略设计模式
通过将Runnable实例作为参数构造Thread,应用到了策略设计模式,将线程控制和业务逻辑分离,Thread只负责控制线程,Runnable作为策略接口,只负责执行业务单元