多线程
2025年11月30日大约 5 分钟
多线程
线程与多线程简介
什么是线程呢,实际上之前我在Python相关内容([Python]进程—函数堵塞的解决方法)的的时候提到过。下面是我当时在网上找到的对线程的解释:
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
那什么是多线程呢:
多线程是指在一个进程中同时运行多个线程,每个线程可以独立执行不同的任务。多线程允许程序在同一时间内处理多个任务,提高了程序的并发性和响应能力。
创建线程
在Java中,创建线程有两种方式:
继承 Thread 类
- 创建一个类继承
Thread类。public class MyThread extends Thread { ...... } - 重写
run()方法,定义线程执行的任务。@Override public void run() { // 线程执行的任务 System.out.println("Thread is running"); } - 创建线程对象并启动线程。
public class Main { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程,调用 run() 方法 } }
注意
- 直接调用
方法不会启动新线程,而是作为普通方法调用,因此仍然是单线程执行。run() - 必须使用
start()方法来启动线程。
- 优点:编码简单
- 缺点:Java 不支持多重继承,继承了
Thread类,就不能再继承其他类。
实现 Runnable 接口
- 创建一个类实现
Runnable接口。public class MyRunnable implements Runnable { ...... } - 实现
run()方法,定义线程执行的任务。@Override public void run() { // 线程执行的任务 System.out.println("Runnable is running"); } - 创建
Thread对象,并将Runnable对象作为参数传递,最后调用Thread对象的start()方法启动线程。public class Main { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); // 启动线程,调用 run() 方法 } }
- 优点:可以实现多重继承,因为 Java 支持类实现多个接口。
- 缺点:线程执行完成后,无法直接获取线程的返回值(返回结果)。
使用 Callable 接口和 FutureTask 类
- 创建一个类实现
Callable接口,并指定返回值类型。import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { ...... } - 实现
call()方法,定义线程执行的任务,并返回结果。@Override public Integer call() { // 线程执行的任务 return 42; // 返回结果 } - 创建
FutureTask对象,并将Callable对象作为参数传递,然后创建Thread对象并启动线程。import java.util.concurrent.FutureTask; public class Main { public static void main(String[] args) throws Exception { MyCallable myCallable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(myCallable); Thread thread = new Thread(futureTask); thread.start(); // 启动线程,调用 call() 方法 // 获取线程执行结果 Integer result = futureTask.get(); System.out.println("Result: " + result); } }
- FutureTask 的构造器及方法
| 构造器 | 说明 |
|---|---|
| FutureTask(Callable<V> callable) | 创建一个 FutureTask 对象,接受一个 Callable 对象作为参数。 |
| 方法 | 说明 |
|---|---|
| V get() | 获取线程执行的结果,如果线程尚未完成,则阻塞等待直到结果可用。 |
- 优点:可以获取线程执行的返回值。
- 缺点:代码相对复杂。
线程安全问题
在多线程环境下,多个线程可能同时修改共享资源,导致数据不一致或程序异常,这就是线程安全问题。
提示
既然有线程安全问题,因此我们就需要解决方法,下面线程同步部分会介绍几种常见的解决方法。
线程同步
- 线程同步的核心思想:让多个线程依次访问共享资源,确保同一时间只有一个线程可以访问共享资源,从而避免数据不一致的问题。
- 线程同步的常见方案:
- 加锁:每次只允许一个线程加锁,加锁后才能访问共享资源,访问完成后自动解锁,其他线程才能继续加锁访问。
同步代码块
- 核心思想: 把访问共享资源的核心代码上锁,确保同一时间只有一个线程可以执行该代码块。
使用 synchronized 关键字定义同步代码块,指定一个锁对象,只有获得该锁的线程才能执行同步代码块。
synchronized (锁对象) {
// 访问共享资源的代码
}注意
对于需要同步的代码块,必须使用同一个锁对象,否则无法实现同步效果。
提示
- 建议使用共享资源作为锁对象,对于实例方法,可以使用
this作为锁对象; - 对于静态方法,可以使用类的字节码(
类名.class)对象作为锁对象。
同步方法
- 核心思想:将整个方法上锁,确保同一时间只有一个线程可以执行该方法。
使用synchronized关键字定义同步方法:
public synchronized void synchronizedMethod() {
// 访问共享资源的代码
}同步方法底层原理
- 同步方法的锁对象由 Java 虚拟机自动管理。
- 对于实例方法,锁对象是当前实例对象(
this)。 - 对于静态方法,锁对象是类的字节码对象(
类名.class)。
lock锁
lock是 Java 提供的一个更灵活的锁机制,位于java.util.concurrent.locks包中。lock是接口,不能直接实例化,需要使用其实现类ReentrantLock。- 使用
lock锁的步骤:- 创建
ReentrantLock对象。 - 在访问共享资源前调用
lock()方法获取锁。 - 在访问共享资源后调用
unlock()方法释放锁。
- 创建
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyClass {
// 使用 final 修饰确保锁对象不可变
private final Lock lock = new ReentrantLock();
public void synchronizedMethod() {
lock.lock(); // 获取锁
try {
// 访问共享资源的代码
} finally { // 写在finally内,确保锁一定会被释放
lock.unlock(); // 释放锁
}
}
}