全部章节 >>>>
本章目录
2.1 线程的概述
程序的运行默认仅包含一个主线程,所有的工作都由主线程承担,当任务繁重耗时,会对于主线程产生很大压力,甚至引起阻塞卡顿
将繁重耗时的任务分出一些子线程去执行,相当于多人做一件事,能大大降低主线程的阻塞,同时提高程序运行效率
2.1.1 进程
几乎所有的操作系统都只是同时运行多个任务,一个任务通常只对应一个程序,每个运行的程序就是一个进程
当一个程序进入到内存中运行时,即变为一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位
进程具有以下特点: 独立性、动态性、并发性
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程
线程之间是独立运行,线程的执行是抢占式的,即当前任何运行的线程在任何时候都有可能被停止,被其他线程抢走对 CPU 的使用权
2.1.2 多线程优势
多线程来实现并发比使用多进程实现并发的性能要高,在执行耗时任务时,开启多个线程能提高执行效率
使用多线程编程具有以下优点:
- 进程之间不共享内存,但线程之间共享内存非常容易
- 创建线程代价较小,能提高任务执行效率
- Java 内置了多线程功能支持,大大简化了多线程编程实现
2.1.3 Thread 类
Java 使用 java.lang.Thread 类表示线程,所有的线程对象都必须是 Thread 类或其子类的实例
通过继承 Thread 类来创建并启动多线程的步骤:
- 定义类继承Thread 类,并重写该类的 run() 方法(run方法是线程的执行主体方法)
- 创建 Thread 子类的实例,即创建线程对象 调用线程对象的 start() 方法,启动该线程、
Thread 类常用方法
方法名 |
作用 |
void setName(String name) |
设置线程的名字 |
String getName() |
返回当前线程的名字 |
Thread currentThread() |
返回当前正在执行的线程对象 |
void start() |
启动线程 |
String getAbsolutePath() |
返回此 File 对象所对应的绝对路径名 |
void run() |
线程一旦执行,便调用此方法 |
boolean isLive() |
判断当前线程是否活跃,活跃返回 true,否则,返回 false |
Thread类实现多线程
示例:实现步骤1
- 创建类继承Thread类,并且重写run方法
- run方法中完成线程执行任务的代码
public class ThreadSimple extends Thread {
private int i = 0;
// 重写 run() 方法
public void run() {
for (; i < 50; i++) {
// getName() 用于获取当前线程的名字
System.out.println(this.getName() + "\t" + i);
}
}
注意:
- 进行多线程编程时,Java 程序运行时默认的主线程即为 main() 方法体,该方法就是主要线程的线程执行体
- 使用继承 Thread 类的方式创建线程类时,多个线程之间无法共享线程类的实例变量(案例线程类中声明的变量i无法被多个线程共享使用)
2.1.4 实践练习
2.2 Runnable接口
2.2.1 Runnable接口
通过继承Thread类虽然可以实现多线程,但是无法实现多线程之间的资源共享,通过Runnable接口实现多线程可以很好解决这个问题
Runnable 接口的使用步骤:
- 定义 Runnable 接口的实现类,并重写该接口的 run() 方法为线程执行主体
- 创建 Runnable 实现类的实例,并以该实例作为 Thread 实例操作的目标
- 创建 Thread 类对象时传入实现Runnable接口的类对象
说明:Runnable对象仅作为 Thread 类对象操作的目标对象,而实际的线程对象依然是 Thread 类的实例
2.2.2 使用 Runnable接口实现多线程
示例:实现步骤1
- 创建类实现Runnable接口,并且重写run方法
- run方法中完成线程执行任务的代码
public class RunnableSimple implements Runnable {
private int i = 1;
// 重写 run() 方法,run() 方法同样是线程执行体
public void run() {
for (; i <= 20; i++) {
// 当实现 Runnable 接口时,获取当前线程,只能使用 Thread.currentThread() 方法
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
}
}
示例:实现步骤2
- 在测试类的main方法中,创建Runnable子类实例对象,作为线程对象执行的目标
- 创建Thread类实例,传入Runnable子类对象(这里可以多个线程使用同一个目标)
- 调用start()方法启动线程
// 创建 RunnableSimple 实例
RunnableSimple rs = new RunnableSimple();
// 创建 Thread 线程实例,该线程操作 rs 对象
Thread thread = new Thread(rs);
thread.setName(“ 线程 A”); // 设置第一个线程的名字
// 创建第 2 个 Thread 线程实例
Thread thread_02 = new Thread(rs);
thread_02.setName(" 线程 B");
// 启动线程 A
thread.start();
// 启动线程 B
thread_02.start();
2.2.3 Thread和Runnable
Thread 类和 Runnable 接口都可以实现多线程,但是Thread方式的子类直接就是线程类,实例化后直接启动;
而Runnable方式创建的类仅为线程的执行提供目标,最终还是依靠Thread类创建线程对象
Runnable方式的优势:
适合多个相同程序代码的线程去处理同一个资源
避免 Java 特性中的单根继承限制、更能体现面向对象特点
增加程序健壮性。在数据被共享时,仍然可以保持代码和数据的分离
经验:在实现多线程时,更推荐大家使用 Runnable 接口
2.2.4 实践练习
2.3 控制线程
2.3.1 线程休眠
Thread 类的 sleep(long millis) 方法可以让当前正在执行的线程暂停多少毫秒,并进入阻塞状态
调用 sleep() 方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会
示例:使用 sleep() 方法休眠线程,实现每隔 500 毫秒出现一颗五角星
public void run() {
// 循环 20 次,打印出 20 颗五角星
for (int i = 0; i < 20; i++) {
try {
// 线程休眠 500 毫秒
Thread.sleep(500);
System.out.print(" ☆ ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.3.2 join线程
Thread 类的 join() 方法可以让一条线程等待另一条线程完成
如:在线程 B 中,调用了线程 A 的 join() 方法后线程 B 则会被挂起,并进入阻塞状态,直到线程 A 执行完毕后才会继续执行线程 B
示例:实现两个线程之间等待效果
分析:实现步骤
- 创建线程类A继承Thread类,重写run方法
- 创建线程类B继承Thread类,B类中需要声明线程A类的对象引用,以便在B类中可以使用线程A类对象
- 线程B类run方法中,调用线程A对象的join()方法,此时线程B会处于等待状态,直到线程A执行完毕
- 创建测试类,创建线程A和B,分别线程A、B并运行
2.3.3 实践练习
2.4 线程的生命周期
当线程被创建启动后,该线程并非一启动就会被执行,也不会一直处于执行状态。在线程的生命周期中,要经过新建>>就绪>>运行>>阻塞>>死亡这 5 种状态
注意:多线程并非同时执行,而是CPU在多个线程之间来回快速切换执行,而线程本身也在运行和阻塞状态之间切换
2.4.1 新建和就绪状态
当程序使用 new 关键字创建线程后,该线程就处于新建状态
当线程对象调用了 start() 方法后,该线程就处于就绪状态,处于就绪状态的线程 并未开始运行,仅表示该线程可以运行,该线程何时开始运行,取决于 JVM中线程调度器的调度
提示:启动线程使用 start() 方法,而不是 run() 方法 永远不要调用线程对象的 run() 方法
2.4.2 线程运行状态
如果处于就绪状态的线程获得了 CPU,开始执行 run() 方法的线程执行体,则该线程处于运行状态
对于单CPU的机器,任何时刻只有一个线程处于运行状态,它会在多个线程之间来回切换执行
对于多CPU的机器,将会有与 CPU 个数相同的线程并行执行
2.4.3 线程阻塞状态
当发生如下情况时,线程将进入阻塞状态:
- 线程调用 sleep() 方法主动放弃占有的 CPU 资源
- 线程调用了一个阻塞式的 I/O 方法,在方法之间,该线程被阻塞
当发生如下特定的情况时,可以解除上述阻塞,让该线程重新进入就绪状态:
- 调用 sleep() 方法的线程经过了指定的时间
- 线程调用的阻塞式 I/O 方法已经返回
2.4.4 线程死亡状态
线程会以如下方式结束,结束后就处于死亡状态
run() 方法执行完成,线程正常结束
线程抛出一个未捕获的 Exception 或 Error
直接调用线程的 stop() 方法来结束该线程,通常不推荐使用该方法
提醒:不要试图对一个已经死亡的线程调用其 start() 方法使其重新启动,否则会抛出异常
2.4.5 线程执行状态图
2.4.6 实践练习
总结:
- 进程一般代表一个应用程序,一个进程中可以包含多个线程
- 合理使用多线程能够提高程序的执行效率,处理高并发应用
- 线程的创建有继承Thread类和实现Runnable接口两种方式,通过Runnable方式可以更加容易实现多线程之间资源共享
- 通过sleep可以使线程进入休眠状态,通过join方法可以让线程处于等待,其他线程执行完毕后继续执行
- 线程生命周期包括:新建 就绪 运行 阻塞 死亡5种状态