全面解析Java(上)------多线程编程:从线程生命周期到并发机制的深度剖析与实践指南
Introduction:收纳技术相关的JAVA知识 JUC
、Thread
、Lock
、I/O
等总结!
文章目录
- Thread
- 线程实现方式
- 线程创建方式
- 线程生命周期
- 新建状态(NEW)
- 可运行状态(RUNNABLE)
- 被阻塞状态(BLOCKED)
- 等待状态(WAITING)
- 计时等待状态(TIMED_WAITING)
- 已终止状态(TERMINATED)
Thread
什么是进程 ?
进程是资源分配的最小单位。(资源包括各种表格、内存空间、磁盘空间) 同一进程中的多条线程将共享该进程中的全部系统资源。
什么是线程 ?
线程是CPU调度的最小单位。线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表组成。 而寄存器可被用来存储线程内的局部变量。
什么是并行和并发 ?
- 并行运行:总线程数≤CPU数量×核心数
- 并发运行:总线程数>CPU数量×核心数(如:有的操作系统CPU线程切换之间用的时间片轮转进程调度算法)
线程优缺点
-
优点
- 创建一个新线程的代价要比创建一个新进程小的多
- 线程之间的切换相较于进程之间的切换需要操作系统做的工作很少
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 等待慢速IO操作结束以后,程序可以继续执行其他的计算任务
- 计算(CPU)密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- IO密集型应用,为了提高性能,将IO操作重叠,线程可以等待不同的IO操作
-
缺点
- 性能损失
- 健壮性降低
- 缺乏访问控制
- 编程难度提高
线程实现方式
实现线程只有一种方式:
- new Thread()
实现线程执行内容有两种方式:
- 继续Thread类:Thread实现了Runable接口
- 实现Runable接口:new Thread(new Runable(){……}),本质是通过Thread的run()进行调用触发
更多实现线程执行内容的方式,只需在此基础上进行封装:
- 线程池创建线程:本质是通过 new Thread() 的方式实现
- 有返回值的Callable创建线程:需要提交到线程池中执行。本质是通过实例化Thread的方式实现
- 定时器Timer:本质是继承自 Thread 类实现
Thread、Runnable和Callable的区别
- Runnable相对于Thread的优势是:避免单继承的局限,适合于资源共享场景
- Thread使用JNI调用(native修饰的start0方法)系统函数来完成start,Runnable则由JVM来实现start,Callable也需要调用Thread.start()启动线程
- 一般情况,多线程中优先选择实现Runnable接口
- Callable能返回任务线程执行结果,而Runable不能返回
- Callable的call方法允许抛出异常,而Runable异常只能在run方法内部消化
Thread.sleep(0)的作用是什么?
由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。
wait()和sleep()的区别?
- wait()来自Object ,sleep()来自Thread
- 调用wait()时线程会释放锁,调用sleep()时线程不会释放对象锁(只是暂时让出CPU的执行权)
- wait()只能在同步控制方法或者同步控制块中使用,sleep()可以在任何地方使用
- wait()可以通过notify()或notifyAll()被结束 ,sleep()只能等待休眠时间到期后才结束
线程创建方式
- 继承Thread类
- 实现Runnable接口
- ExecutorService、Callable、Future有返回值线程
- 基于线程池的方式
线程生命周期
Linux中线程状态一共有5种:
- 初始状态(New):对应 Java中的 NEW 状态
- 可运行状态(Ready):对应 Java中的 RUNNBALE 状态
- 运行状态(Running):对应 Java中的 RUNNBALE 状态
- 等待状态(Waiting):该状态在 Java中被划分为了 BLOCKED、WAITING、TIMED_WAITING 三种状态
- 终止状态 (Terminated):对应 Java中的 TERMINATED 状态
Java中线程状态一共有6种(生命周期):
- New(新创建):新创建了一个线程对象,但还没有调用start()方法
- Runnable(可运行):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其它线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)
- Blocked(被阻塞):表示线程阻塞于锁
- Waiting(等待):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
- Timed Waiting(计时等待):该状态不同于WAITING,它可以在指定的时间后自行返回
- Terminated(被终止):表示该线程已经执行完毕。线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。线程状态切换:
新建状态(NEW)
New 表示线程被创建但尚未启动的状态:当我们用 new Thread() 新建一个线程时,如果线程没有开始运行 start() 方法,所以也没有开始执行 run() 方法里面的代码,那么此时它的状态就是 New。而一旦线程调用了 start(),它的状态就会从 New 变成 Runnable,也就是状态转换图中中间的这个大方框里的内容。
可运行状态(RUNNABLE)
Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running 和 Ready,也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
所以,如果一个正在运行的线程是 Runnable 状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是 Runnable,因为它有可能随时被调度回来继续执行任务。
被阻塞状态(BLOCKED)
首先来看最简单的 Blocked,从箭头的流转方向可以看出,从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized 保护的代码时没有抢到 monitor 锁,无论是进入 synchronized 代码块,还是 synchronized 方法,都是一样。当处于 Blocked 的线程抢到 monitor 锁,就会从 Blocked 状态回到Runnable 状态。
等待状态(WAITING)
线程进入 Waiting 状态有三种可能性:
- 没有设置 Timeout 参数的 Object.wait() 方法
- 没有设置 Timeout 参数的 Thread.join() 方法
- LockSupport.park() 方法
Blocked 仅仅针对 synchronized monitor 锁,可是在 Java 中还有很多其他的锁,比如 ReentrantLock,如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态,因为本质上它执行了 LockSupport.park() 方法,所以会进入 Waiting 状态。同样,Object.wait() 和 Thread.join() 也会让线程进入 Waiting 状态。
Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。
计时等待状态(TIMED_WAITING)
在 Waiting 上面是 Timed Waiting 状态,这两个状态是非常相似的,区别仅在于有没有时间限制,Timed Waiting 会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。以下情况会让线程进入 Timed Waiting 状态。
- 设置了时间参数的 Thread.sleep(long millis) 方法
- 设置了时间参数的 Object.wait(long timeout) 方法
- 设置了时间参数的 Thread.join(long millis) 方法
- 设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法
已终止状态(TERMINATED)
线程会以下面三种方式结束,结束后就是终止状态:
-
正常结束:run()或 call()方法执行完成,线程正常结束
-
异常结束:线程抛出一个未捕获的 Exception 或 Error
-
调用 stop:直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用