Pixiv - KiraraShss
1521 字
8 分钟
多线程
一、 线程相关概念
1.1 程序、进程与线程
-
程序:是为完成特定任务、用某种语言编写的一组指令的集合,即我们写的静态代码。
-
进程:
- 指运行中的程序。如启动QQ,操作系统会为其分配独立的内存空间。
- 进程是程序的一次执行过程,是动态的,有其产生、存在和消亡的生命周期。
-
线程:
- 由进程创建,是进程的一个实体。
- 一个进程可以拥有多个线程(如一个QQ进程可以同时打开多个聊天窗口)。
1.2 单线程、多线程、并发与并行
- 单线程:同一时刻,只允许执行一个线程。
- 多线程:同一时刻,可以执行多个线程。
- 并发:单核CPU实现的多任务。同一时刻,多个任务交替执行,造成“同时”的错觉。
- 并行:多核CPU实现的多任务。同一时刻,多个任务真正同时执行。
二、 线程的创建与使用
2.1 创建线程的两种方式
- 继承 Thread 类,重写 run 方法。
- 实现 Runnable 接口,重写 run 方法。
2.2 方式一:继承 Thread 类
-
步骤:
- 定义一个类继承 Thread。
- 重写 run 方法,在其中编写线程要执行的任务。
- 创建该类的实例。
- 调用实例的 start() 方法启动线程。
-
注意:
- start() 方法会调用底层的 start0() 方法,使线程进入可运行状态,由CPU调度执行。
- 直接调用 run() 方法仅是普通方法调用,不会启动新线程。
2.3 方式二:实现 Runnable 接口
-
步骤:
- 定义一个类实现 Runnable 接口。
- 重写 run 方法。
- 创建该类的实例。
- 将此实例作为参数传递给 Thread 对象的构造器,创建 Thread 对象。
- 调用 Thread 对象的 start() 方法启动线程。
-
原理:使用了代理模式。Thread 类本身实现了 Runnable 接口,其 run 方法会调用传入的 Runnable 对象的 run 方法。
-
优势:
- 避免Java单继承的限制。
- 更适合多个线程共享同一个资源的场景(只需创建一个 Runnable 实例,传递给多个 Thread 对象)。
三、 线程常用方法
3.1 第一组常用方法
- setName(String name) / getName():设置/获取线程名称。
- start():启动线程,使其进入可运行状态。
- run():线程执行体,由JVM调用。
- setPriority(int newPriority) / getPriority():设置/获取线程优先级(范围1-10)。
- sleep(long millis):静态方法。让当前线程休眠指定的毫秒数。
- interrupt():中断线程,但并非强制结束。通常用于中断正在 sleep 或 wait 的线程,使其抛出 InterruptedException。
3.2 第二组常用方法
-
yield() :线程的礼让。让出CPU,给其他线程执行机会,但礼让成功与否取决于CPU调度,不一定成功。
-
join() :线程的插队。调用此方法的线程会阻塞当前线程(如主线程),直到调用者执行完毕,当前线程才继续执行。
- 应用场景:主线程需要等待子线程执行完特定任务后再继续。
3.3 用户线程与守护线程
-
用户线程(工作线程) :线程任务执行完毕或通过通知方式结束。
-
守护线程:为工作线程服务。当所有用户线程结束时,守护线程自动结束。
- 典型例子:垃圾回收线程。
- 设置方法:线程对象.setDaemon(true); (必须在 start() 前调用)。
四、 线程的生命周期
JDK中 Thread.State 枚举定义了线程的六种状态:
- NEW(新建) :线程对象已创建,但尚未调用 start()。
- RUNNABLE(可运行) :线程正在JVM中执行,或已就绪等待CPU调度。对应操作系统中的 Ready(就绪) 和 Running(运行) 状态。
- BLOCKED(阻塞) :线程等待获取一个监视器锁(如进入 synchronized 代码块失败)。
- WAITING(无限等待) :线程等待另一个线程执行特定动作(如 Object.wait())。
- TIMED_WAITING(计时等待) :线程在指定时间内等待(如 Thread.sleep(long))。
- TERMINATED(终止) :线程执行完毕,已退出。
五、 线程的同步
5.1 同步的必要性
在多线程环境下,当多个线程同时访问共享资源(如卖票系统的票数)时,可能导致数据不一致(超卖、重复卖)等问题。
5.2 同步机制 - Synchronized
保证在任一时刻,最多只有一个线程访问同步代码,确保数据完整性。
- 同步代码块:
synchronized(对象) { // 得到对象的锁才能进入 // 需要同步的代码}public synchronized void method() { // 需要同步的代码}5.3 互斥锁
-
每个Java对象都有一个互斥锁(Monitor Lock) 标记。
-
synchronized 关键字与对象的互斥锁关联。
-
锁对象的选择:
- 对于非静态同步方法,锁对象默认为 this(当前对象实例)。
- 对于静态同步方法,锁对象默认为 当前类.class。
- 对于同步代码块,锁对象是 synchronized 后括号里指定的对象。
- 核心原则:要求多个线程竞争的锁对象必须是同一个。
5.4 释放锁与不释放锁的操作
-
会释放锁:
- 同步代码块/方法执行完毕。
- 同步代码块/方法中遇到 break、return。
- 同步代码块/方法中出现未处理的 Error 或 Exception。
- 同步代码块/方法中执行了 wait() 方法。
-
不会释放锁:
- 调用 Thread.sleep() 或 Thread.yield() 暂停线程。
- 其他线程调用了该线程的 suspend() 方法(该方法已废弃,应避免使用)。
5.5 线程死锁
- 定义:多个线程都占用了对方所需的锁资源,并且互相等待对方释放,导致所有线程都无法继续执行。
- 产生条件:互斥使用、不可抢占、请求和保持、循环等待。
- 解决:在编程中应尽量避免。可以通过规定锁的获取顺序、使用 tryLock 等机制来预防。2
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!