1.概念
⼀个线程就是⼀个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码.
还是回到我们之前的银行的例子中。之前我们主要描述的是个人业务,即一个人完全处理自己的业务。我们进一步设想如下场景: 一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。 如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。 此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(MainThread)。
用生活例子理解:线程就像 工厂里一条独立的生产线。工厂要完成很多任务,单靠一条生产线(比如只生产杯子)效率低。有多条生产线(线程),就能同时生产杯子、盘子等不同物品,让工厂整体效率变高。放在电脑里,程序通过线程实现 "同时做几件事",比如一边下载文件(一个线程)、一边播放音乐(另一个线程)
(1)为什么要有线程
官方解释
首先,"并发编程" 成为 "刚需":
- 单核 CPU 的发展遇到瓶颈,提升算力需依赖多核 CPU,而并发编程能更充分利用多核 CPU 资源。
- 部分任务存在 "等待 IO" 的场景(如网络请求、文件读写),利用并发编程可在等待 IO 的时间里处理其他工作,避免资源闲置。
其次,虽多进程也能实现并发,但线程比进程更轻量:
- 线程的创建速度比进程更快。
- 线程的销毁速度比进程更快。
- 操作系统对线程的调度速度比进程更快。
最后,即便线程已较轻便,仍需进一步优化,因此衍生出 "线程池"(ThreadPool)和 "协程"(Coroutine)等技术。
理解
生活中,线程可类比餐厅里的多个服务员:若只有 1 个服务员(单线程),一次只能为 1 位顾客完成点单、上菜、结账全流程,效率极低;但如果有多个服务员(多线程),就能同时为不同顾客分工服务 ------ 有的负责点单、有的负责上菜、有的负责结账,餐厅可同时处理更多顾客需求,整体服务效率大幅提升。
**(2)**进程和线程的区别
• 进程是包含线程的. 每个进程至少有⼀个线程存在,即主线程。
• 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间
比如之前的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。
• 进程是系统分配资源的最雄安单位,线程是系统调度的最小单位。
• ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带走(整个进程崩溃).

理解
1. 轻量级进程(线程的本质)
线程是 "轻量级进程",同一进程的多个线程共享同一份系统资源(如内存、文件描述符表等)。
- 优势:创建线程时省去申请资源的开销,销毁线程时省去释放资源的开销,效率远高于进程。
2. 独立执行流
每个线程都是独立的执行流,可独自参与 CPU 调度(即系统会单独给线程分配 CPU 时间片)。
3. 进程与线程的角色区分
- 进程 :是系统分配资源的基本单位(负责申请内存、文件句柄等资源)。
- 线程 :是系统调度执行的基本单位(负责具体任务的执行,由操作系统调度到 CPU 上运行)。
- 关系:一个进程可包含多个线程,这些线程共享进程的资源,同时各自独立执行任务。
(3)进程和线程核心区别

- 资源占用:比如电脑上同时运行的音乐播放器程序和浏览器程序,它们是两个不同的进程,有各自独立的资源。而线程共享所属进程的资源,相当于大房子里不同的人,共享房子里的空间和设施 。
- 独立性:进程相互独立,不同进程之间不能直接共享数据,就像不同房子之间是隔开的。线程之间的隔离性没那么强,同一进程内的线程可以直接访问共享数据,比如一个程序里多个线程可以同时访问程序中的全局变量。
- 上下文切换开销:进程切换时,因为各自独立,要保存和恢复大量信息,开销大,就像把一个大房子里的所有东西都收拾好放到一边,再把另一个大房子的东西拿出来布置,很麻烦。线程切换相对简单,因为共享进程的资源,开销小,类似大房子里不同人的位置变换,没那么复杂。
- 控制方式:进程的创建和管理一般通过操作系统的指令或接口来操作;线程在程序里就可以通过代码灵活创建、销毁和管理,比如在 Java 中可以用代码轻松创建新线程。

(4)Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念.操作系统内核实现了线程这样的机制,并且对用户层提供了⼀些API供用用户使用(例如Linux的pthread库).
Java 标准库中Thread类可以视为是对操作系统提供的API进行了进⼀步的抽象和封装.

联系:
Java 线程需要依赖操作系统线程来实现底层的运行和调度。就像你用手机 APP(Java 线程),APP 的各种操作最终要靠手机系统(操作系统线程)来执行。
Java 虚拟机(JVM )在运行时会和操作系统交互,把 Java 线程映射到操作系统线程上 ,让 Java 线程能利用系统资源执行任务。
区别:
- 调度方式:操作系统线程由操作系统内核调度,而 Java 线程是由 JVM 调度。可以理解为,操作系统线程是听从操作系统这个 "大管家" 的安排,Java 线程则是在 JVM 这个 "小管家" 的管理下运行 。
- 上下文切换成本:Java 线程切换时,要先在 JVM 中保存和恢复线程状态等信息,开销比操作系统线程切换更高。比如切换两个操作系统线程像在两个相邻房间快速走动;而切换 Java 线程,就像在房间里还要先整理好东西再出去,更费时间。
- 系统资源占用:操作系统线程有自己的线程栈、线程控制块等系统资源;Java 线程除了这些,还需要 Java 堆内存和栈内存,所以 Java 线程消耗的系统资源略多一些 。
2.第⼀个多线程程序
2.1感受多线程程序和普通程序的区别:
• 每个线程都是⼀个独立的执行流
• 多个线程之间是"并发"执行的
java
package thread;
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello thread");
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t = new MyThread();
// 真正在系统中创建出一个线程
t.start();
// t.run();
}
}
2.2 start 和 run 的 区别是什么呢?

"异步" 核心是:两个线程的执行流程是 "并行" 的,调用者(主线程)不需要等待被调用者(新线程的 run())完成,各自推进自己的逻辑。
"阻塞" 指的是一个线程(或进程)因等待某个资源或事件(如网络请求、文件读写、锁获取等)而暂时停止执行,直到该资源可用或事件发生,才会继续执行的状态。
2.3 来感受一下两个进程的效果
java
package thread;
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello thread");
while (1==1 ){
System.out.println("hello thread");
}
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t = new MyThread();
// 真正在系统中创建出一个线程
t.start();
while (1 == 1) {
System.out.println("hello main");
}
}
}
主线程会继续执行自己后续的代码
新线程则独立执行 run() 方法中的逻辑
两者是并行运行的关系。
2.4 Thread.sleep 这个方法
java
Thread.sleep(1000);
//1000 单位是毫秒
这是一个静态的方法
是 Java 中用于让当前线程暂停执行指定时间的方法,属于线程控制的核心 API 之一。它的作用是让线程进入 "休眠" 状态,暂时让出 CPU 使用权,直到指定时间结束后再重新进入就绪状态,等待调度。

InterruptedException 是 Java 中的一个受检异常(checked exception),通常在线程的阻塞状态被打断时抛出 。它的核心含义是:当前线程正在等待、休眠或阻塞时,被其他线程通过 interrupt() 方法强制 "唤醒",以响应中断请求。
Java 规定:子类重写父类方法时,声明的受检异常范围不能超过父类方法。具体来说:
- 子类方法可以不抛出任何异常(即使父类方法有异常声明)。
- 子类方法可以抛出父类方法声明的异常的子类(缩小异常范围)。
- 子类方法不能抛出父类方法未声明的受检异常(扩大异常范围)
重写 Thread 类的 run() 方法不能throws ,只能try -catch,但是try - catch 还是向上抛出异常,只不过是换了个形式,实际开发中就啥也没干,就不科学

但是在main 方法中的是可以进行throws, 点击第一个就是将异常添加到方法签名中

在实际开发中,我们解决异常的方法:
- 记录异常信息作为日志,在发现异常的时候根据日志发现问题
- 重启能解决90%的问题
- 通过短信/微信/电话/报警通知程序员,马上处理这个问题
java
package thread;
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello thread");
while (1==1 ){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello thread");
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
// 真正在系统中创建出一个线程
t.start();
while (1 == 1) {
Thread.sleep(1000);
System.out.println("hello main");
}
}
}
执行后,我们能发现,谁先执行,谁后执行都有可能,这个是无法预测的
调度顺序:是由操作系统的内核的调度器控制的,没有办法再程序应用编程控制的(没有提供API)
2.5不知道JDK 安装到哪里了?

点击进入到该界面

2.6 jconsole.exe
jconsole.exe 是 Java 开发工具包(JDK)提供的一款图形化监控工具 ,全称为 Java Console,主要用于监控和管理 Java 应用程序(包括本地和远程的 Java 进程)
在路经中找放到它,点击

只会列出Java写的进程,其他语言的进程就不会出现

找到自己的项目然后连接

这是当前进程的线程,启动任何Java进程都会自带这些线程


可以在里面的选择哪一个线程,直接观察即可

3.创建多线程的方法
3.1继承Thread ,重写 run 方法
3.2实现runnable ,重写 run 方法
- 实现Runnable接口
java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
2.创建Thread类实例,调用Thread的构造方法时将Runnable对象作为target参数.
java
Thread t = new Thread(new MyRunnable());
3.调用start方法
java
package thread;
import com.sun.source.tree.Tree;
public class demo2 {
public static void main(String[] args) throws InterruptedException {
// 创建任务,并设置终止标志
MyRunnable task = new MyRunnable();
// 创建线程(传入任务)
Thread t = new Thread(task);
t.start();
while(true) {
Thread.sleep(1000);
System.out.println("hello ,main");
}
}
}
class MyRunnable implements Runnable {
public void run() {
while (1 == 1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello runnable ");
}
}
}
3.3Thread 与 Runnable 的分工设计思路
- 线程创建的核心载体 :最终必须通过
Thread类来真正创建操作系统级别的线程。 - 任务逻辑的分离表示 :线程要执行的 "具体做什么"(任务逻辑),推荐通过
Runnable接口来定义,而非直接重写Thread类的run方法。 - 任务定义的位置选择 :明确了两种方式的区别 ------ 任务可以定义在
Thread类内部(通过继承Thread并重写run),也可以定义在Thread类外部的Runnable实现类中(更推荐,实现 "任务与线程控制的分离")。
通过 Runnable 实现 "任务与线程解耦合" 的设计优势,可提取如下信息:
-
核心概念:解耦合让 "要执行的任务本身" 与 "线程这个概念" 分离,不再强绑定。
-
带来的好处:便于代码维护与扩展 后续若需变更任务的执行方式(比如不通过线程执行,改用其他方式),采用
Runnable方案时,代码修改会更简单。