一.认识线程(Thread)
1. 1) 线程是什么
⼀个线程就是⼀个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码,main()⼀般被称为主线程(Main Thread)。
1. 2) 为啥要有线程
首先, “并发编程” 成为 “刚需”.
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源.
- 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做⼀些其他的工作, 也需要用到并发编程. 其次,
虽然多进程也能实现 并发编程, 但是线程比进程更轻量. - 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)
关于线程池我们后面再介绍. 关于协程的话题我们此处暂时不做过多讨论.
1.3) 进程和线程的区别
1.进程是包含线程的. 每个进程至少有⼀个线程存在,即主线程。
2.进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.
3.进程是系统分配资源的最小单位,线程是系统调度的最小单位。
4.⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带走(整个进程崩溃)
1.4) Java的线程和操作系统线程的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了⼀些API供用户使用(例如Linux的pthread库) 例如:Java标准库Thread的类可以视为是对操作系统提供的API进行了进⼀步的抽象和封装.
二.创建线程
方法1:继承Thread类
继承Thread来创建⼀个线程类,直接使用this就表示当前线程对象的引用
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
public class Test {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
运行结果:
![图片[1]-【java】多线程](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/07/image-12-1024x288.png)
方法2:实现Runnable接口
实现Runnable接口,this表示的是 MyRunnable 的引用.需要使用Thread.currentThread()
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
运行结果:
![图片[1]-【java】多线程](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/07/image-12-1024x288.png)
方法3:匿名内部类创建Thread子类对象
public class Test {
public static void main(String[] args) {
// 使⽤匿名类创建 Thread ⼦类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使⽤匿名类创建 Thread ⼦类对象");
}
};
}
}
方法4:匿名内部类创建Runnable子类对象
public class Test {
public static void main(String[] args) {
// 使⽤匿名类创建 Runnable ⼦类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使⽤匿名类创建 Runnable ⼦类对象");
}
});
}
}
方法5:lambda表达式创建Runnable子类对象
public class Test {
public static void main(String[] args) {
// 使⽤匿名类创建 Runnable ⼦类对象
// 使⽤ lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使⽤匿名类创建 Thread ⼦类对象");
});
}
}
三.Thread类及其方法
Thread 类是 JVM 用来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。而Thread 类的对象就是用来描述⼀个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
3.1Thread的常见构造方法
| 方法 | 说明 |
|---|---|
| 创建线程对象 |
| 使用 Runnable 对象创建线程对象 |
| 创建线程对象,并命名 |
| 使用 Runnable 对象创建线程对象,并命名 |
| 线程可以被用来分组管理,分好的组即为线程组 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
3.2Thread的几个常见属性
| 属性 | 获取方法 |
|---|---|
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |
- ID是线程的唯⼀标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的⼀个情况,下面我们会进⼀步说明
- 优先级高的线程理论上来说更容易被调度到
关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行。 是否存活,即简单的理解,为run方法是否运行结束了
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我即将死去");
});
System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());
System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());
System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());
System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());
System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());
System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());
thread.start();
}
}
运行结果:
![图片[3]-【java】多线程](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/07/image-14-1024x467.png)
3.3获取当前线程引用
| 方法 | 说明 |
|---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
3.4休眠当前线程
| 方法 | 说明 |
|---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠当前线程 millis 毫秒和 nanos 纳秒 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
四:线程的状态
4.1线程状态
- NEW(新建):
- 线程已被创建,但尚未启动。
- 处于这个状态的线程还没有开始执行。
- RUNNABLE(可运行):
- 线程正在JVM中执行,或者正在等待CPU时间片以便执行。
- 这个状态可以分为两种情况:
- 正在工作中:线程正在使用CPU执行。
- 即将开始工作:线程已准备好执行,但正在等待CPU时间片。
- BLOCKED(阻塞):
- 线程因等待一个监视器锁而暂停执行。
- 线程将一直等待,直到它能够获取到所需的锁。
- WAITING(等待):
- 线程正在等待另一个线程执行特定的操作(如通知或中断)。
- 线程不会自动唤醒,必须等待其他线程调用
notify()或notifyAll()方法。
- TIMED_WAITING(定时等待):
- 线程在等待另一个线程执行操作或超时。
- 线程将在指定的时间后自动唤醒,或者在其他线程调用
notify()或notifyAll()方法时唤醒。
- TERMINATED(终止):
- 线程已完成执行。
- 线程已经运行完毕或被其他线程中断。
4.2线程状态和状态转移的意义
![图片[4]-【java】多线程](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/07/bdc755d2fd2324a2ddcfd702b615a7db-1024x684.jpeg)
4.3观察线程的状态和转移
示例1:NEW(新建) 、 RUNNABLE(可运行) 、 TERMINATED(终止) 状态的转换
public class ThreadStateTransfer {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 1000_0000; i++) {
}
}, "李四");
System.out.println(t.getName() + ": " + t.getState());;
t.start();
while (t.isAlive()) {
System.out.println(t.getName() + ": " + t.getState());;
}
System.out.println(t.getName() + ": " + t.getState());;
}
}
运行结果
![图片[5]-【java】多线程](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/07/image-15-1024x266.png)
![图片[6]-【java】多线程](https://8cltw.oss-cn-hongkong.aliyuncs.com/wp-content/uploads/2025/07/image-16.png)
示例2: WAITING(等待) 、 BLOCKED(阻塞) 、 TIMED_WAITING (定时等待)状态的转换
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("hehe");
}
}
}, "t2");
t2.start();
}
使用jconsole可以看到t1的状态是TIMED_WAITING,t2的状态是BLOCKED
结论:
1.BLOCKED表示等待获取锁,WAITING和TIMED_WAITING表示等待其他线程发来通知.
2.TIMED_WAITING线程在等待唤醒,但设置了时限;WAITING线程在无限等待唤醒
五:多线程带来的的风险-线程安全(重点)
5.1 线程安全的概念
线程安全指的是在多线程环境下,代码的运行结果符合预期,即与单线程环境下的运行结果相同。这意味着程序在多线程环境中能够正确处理共享数据,而不会导致数据不一致或其他意外行为。
5.2 线程不安全的原因
线程不安全的主要原因是线程调度的随机性。这种随机性导致程序在多线程环境下的执行顺序存在变数,使得程序的行为难以预测。程序员必须确保代码在任意执行顺序下都能正常工作,以保证线程安全。
5.3 线程的几大特性
5.3.1 原子性
原子性指的是代码在执行过程中不会被其他线程打断,从而保证了操作的完整性。这有时也被称为同步互斥,意味着操作是互斥的,即同一时间只能有一个线程执行该操作。
5.3.2 可见性
可见性指的是一个线程对共享变量的修改能够及时被其他线程看到。这是通过Java内存模型(JMM)来实现的,JMM定义了主内存和工作内存之间的交互规则,确保了变量值的可见性。
- Java内存模型(JMM):JMM是Java虚拟机规范中定义的,目的是屏蔽不同硬件和操作系统的内存访问差异,以实现Java程序在各种平台下的一致并发效果。
- 主内存与工作内存:线程之间的共享变量存储在主内存中。每个线程都有自己的工作内存,用于存储共享变量的副本。线程读取或修改共享变量时,需要在主内存和工作内存之间进行数据同步。
5.3.3 指令重排序
指令重排序是指编译器或CPU为了优化性能,可能会改变指令的执行顺序。在单线程环境下,这种优化通常不会影响程序的逻辑。但在多线程环境下,由于线程间的执行顺序更加复杂,编译器很难预测代码的执行效果,因此激进的重排序可能导致优化后的逻辑与之前不等价。
- 代码重排序示例:
1. 去前台取下U盘 2. 去教室写10分钟作业 3. 去前台取下快递- 在单线程情况下,JVM或CPU可能会按1->3->2的方式执行,以减少前台的访问次数。但在多线程环境下,这种重排序可能会导致问题,因为多线程的代码执行复杂度更高,编译器难以在编译阶段预测代码的执行效果。
指令重排序是一个复杂的话题,涉及到CPU和编译器的底层工作原理,需要更深入的讨论和理解。










暂无评论内容