1. 线程相关概念
1.1 线程和进程的关系
-
从构成关系上看,线程是进程中的一个实体,一个进程中至少有一个线程,线程本身不能独立存在。
-
从程序角度来看,进程是代码在数据集合上的一次运行活动,线程是进程的一个执行路径。
-
从操作系统资源分配上看,进程是系统进行资源分配和调度的基本单位,线程是CPU分配的基本单位。
-
从资源访问的角度上看,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。如下图所示
1.2 线程上下文切换
-
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这 个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
-
JVM 使用程序计数器来记住下一条指令的执行地址,是线程私有的
-
线程上下文频繁切换会影响性能,所以不是线程越多越好。这也是为什么在cpu核心数量只有一个的系统上,并发的效果甚至不如串行。
1.3 线程优先级
-
现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需 要多或者少分配一些处理器资源的线程属性。
-
Java 中 Thread 对象有一个整形变量 priority 来控制优先级,可以通过 setPriority() 来设置这个值, 但是 Java 中线程优先级的设置效果不明显,操作系统可能会忽略这个线程优先级的设置.
1.4 线程状态
1.4.1 操作系统线程状态(五种)
状态名称 | 说明 |
---|---|
初始状态 | 仅在语言层面创建了线程对象,还未与操作系统线程关联 |
可运行状态 | 指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行 |
运行状态 | 指获取了CPU时间片运行中的状态 |
阻塞状态 | 如果调用了阻塞API,这时线程实际不会用到CPU,会导致上下文切换,进入阻塞状态。等操作完毕,由操作系统唤醒阻塞的线程,转换至可运行状态 |
终止状态 | 表示线程已经执行完毕,生命周期已经结束,不会转化为其他状态 |
(图片源自网络)
1.4.2 Java线程状态(六种)
根据 Thread.State 枚举,分为六种状态
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start() 方法 |
RUNNABLE | 运行状态,Java 线程将操作系统中的就绪、运行、阻塞三种状态笼统地称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知和中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
Java线程状态变迁
(图片来自《Java并发编程的艺术》)
1.5 线程死锁
- 四个必要条件:互斥条件,请求并持有条件,不可剥夺条件,环路等待条件。
- 避免死锁只需要破环至少一个必要条件,目前只有请求并持有和环路等待条件可以被破环。
- 资源申请的有序性原则可以避免死锁。
2.创建线程的三种方法
2.1 方法一:继承 Thread ,重写 run 方法
public class ThreadTest1 {
// 继承 Thread 并重写 run 方法
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("t1 run");
}
}
public static void main(String[] args) {
// 创建线程
MyThread thread = new MyThread();
// 启动线程
thread.start()