多线程的核心概念
在计算机程序中,线程是CPU调度的基本单位,而进程则是资源分配的基本单位,多线程是指在一个进程内创建多个线程,这些线程共享进程的内存空间和资源(如文件句柄、网络连接等),但每个线程拥有独立的执行栈和程序计数器,Java作为一门支持多线程的语言,通过内置的java.lang.Thread类及相关API,为开发者提供了便捷的多线程实现方式。

多线程的主要优势在于提高程序的执行效率和响应速度,在Web服务器中,使用多线程可以同时处理多个客户端请求;在图形界面程序中,通过多线程可以将耗时操作(如文件下载、数据计算)放到后台线程执行,避免界面卡顿,但需要注意的是,多线程也会带来线程安全问题(如数据竞争、死锁)和额外的上下文切换开销,因此合理使用多线程至关重要。
Java开辟多线程的三种基础方式
Java中创建多线程主要有三种方式:继承Thread类、实现Runnable接口、实现Callable接口,这三种方式各有特点,适用于不同的场景。
继承Thread类
Thread类是Java中用于表示线程的类,继承Thread类并重写其run()方法是最直接的创建线程的方式。
实现步骤:
- 定义一个类继承
Thread类; - 重写
run()方法,在方法中编写线程要执行的代码; - 创建该类的实例,调用
start()方法启动线程(注意:直接调用run()方法不会启动新线程,而是在当前线程中执行run()中的代码)。
示例代码:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
}
}
优缺点:
- 优点:实现简单,可以直接使用
Thread类的方法(如getName()、currentThread()); - 缺点:Java不支持多继承,如果继承了
Thread类就无法再继承其他类,灵活性较差。
实现Runnable接口
Runnable接口是一个函数式接口,其中定义了一个抽象方法run(),通过实现Runnable接口,可以将线程的任务与线程本身分离,更符合“面向接口编程”的思想。
实现步骤:
- 定义一个类实现
Runnable接口,重写run()方法; - 创建
Thread对象时,将Runnable实例作为参数传入; - 调用
Thread对象的start()方法启动线程。
示例代码:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
}
}
优缺点:

- 优点:避免了单继承的限制,多个线程可以共享同一个
Runnable实例(适合多线程处理同一资源的情况); - 缺点:
Runnable接口中没有提供线程管理的方法(如获取线程名称、优先级等),需要通过Thread对象来操作。
实现Callable接口
Callable接口是Java 5新增的接口,与Runnable类似,但Callable的call()方法可以有返回值,并且可以抛出异常。Callable通常与Future配合使用,用于获取线程执行的结果。
实现步骤:
- 定义一个类实现
Callable接口,重写call()方法(返回值为泛型类型); - 创建
ExecutorService线程池(推荐)或使用FutureTask包装Callable对象; - 提交任务到线程池,获取
Future对象,通过Future的get()方法获取结果。
示例代码:
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
return sum;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new MyCallable());
System.out.println("线程执行结果: " + future.get()); // 获取结果(会阻塞直到线程完成)
executor.shutdown(); // 关闭线程池
}
}
优缺点:
- 优点:支持返回结果和异常处理,功能比
Runnable更强大; - 缺点:使用相对复杂,需要配合
Future或线程池使用。
线程的生命周期与状态管理
Java中的线程具有多种状态,通过Thread.State枚举定义,包括:
- 新建(NEW):线程被创建但尚未启动(如
new Thread()后未调用start()); - 就绪(RUNNABLE):线程调用
start()后,等待CPU调度(注意:Java中将“就绪”和“运行”统一为RUNNABLE状态,因为线程的运行由操作系统调度,Java无法直接区分); - 阻塞(BLOCKED):线程等待获取锁(如
synchronized锁),进入阻塞状态; - 等待(WAITING):线程等待其他线程显式唤醒(如调用
wait()、join()、LockSupport.park()); - 超时等待(TIMED_WAITING):线程等待指定时间后自动唤醒(如调用
sleep(long millis)、wait(long timeout)); - 终止(TERMINATED):线程执行完成或因异常退出。
状态转换示例:
- 新建 → 就绪:调用
start()方法; - 就绪 → 运行:CPU调度线程;
- 运行 → 阻塞:尝试获取锁但锁被其他线程占用;
- 阻塞 → 就绪:获取到锁;
- 运行 → 等待:调用
wait()方法; - 等待 → 就绪:其他线程调用
notify()或notifyAll(); - 运行 → 终止:
run()方法执行完成或抛出未捕获异常。
线程同步与通信机制
当多个线程共享同一资源时,可能会出现“线程安全问题”(如数据不一致、脏读),Java提供了多种同步机制来保证线程安全。
synchronized关键字
synchronized是Java内置的同步机制,可以修饰方法、代码块,确保同一时间只有一个线程可以访问同步代码。
- 修饰方法:相当于对当前对象(
this)加锁; - 修饰静态方法:相当于对类对象(
Class对象)加锁; - 修饰代码块:可以指定锁对象(如
synchronized (this)或synchronized (lockObj))。
示例代码:
class Counter {
private int count = 0;
public synchronized void increment() { // 同步方法
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终计数: " + counter.getCount()); // 输出2000
}
}
Lock接口
java.util.concurrent.locks.Lock接口提供了更灵活的锁机制,支持尝试获取锁(tryLock())、可中断获取锁(lockInterruptibly())和公平锁等特性,常用的实现类是ReentrantLock。

示例代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 加锁
try {
count++;
} finally {
lock.unlock(); // 释放锁(必须放在finally中,避免死锁)
}
}
public int getCount() {
return count;
}
}
线程间通信:wait/notify/notifyAll
wait()、notify()、notifyAll()是Object类的方法,用于线程间的等待和唤醒。
wait():当前线程释放锁并进入等待状态,直到其他线程调用notify()或notifyAll()唤醒;notify():随机唤醒一个等待该锁的线程;notifyAll():唤醒所有等待该锁的线程。
注意:这些方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException。
Java高级线程工具:线程池与异步编程
直接创建线程(new Thread())会带来性能问题(频繁创建和销毁线程),因此Java推荐使用线程池来管理线程。
Executor框架
java.util.concurrent.Executor是线程池的顶层接口,核心子接口是ExecutorService,通过Executors工厂类可以创建不同类型的线程池:
newFixedThreadPool(int n):固定大小线程池,线程数固定,超时线程会被回收;newCachedThreadPool():可缓存线程池,线程数根据任务动态调整(60秒无任务则回收);newSingleThreadExecutor():单线程线程池,所有任务按顺序执行;newScheduledThreadPool(int corePoolSize):定时任务线程池,支持延迟和周期性执行。
示例代码:
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("线程 " + Thread.currentThread().getName() + " 执行任务 " + taskId);
});
}
executor.shutdown();
}
}
CompletableFuture(异步编程)
Java 8引入的CompletableFuture是Future的增强版,支持函数式编程和异步链式调用,可以更优雅地处理异步任务。
示例代码:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return "异步任务结果";
}).thenAccept(result -> {
System.out.println("收到结果: " + result);
});
System.out.println("主线程继续执行...");
try { Thread.sleep(2000); } catch (InterruptedException e) {} // 等待异步任务完成
}
}
多线程开发的注意事项与最佳实践
- 避免死锁:死锁是指多个线程互相等待对方持有的资源,导致所有线程都无法继续执行,避免死锁的常见方法:避免嵌套锁、按固定顺序获取锁、使用
tryLock()设置超时; - 合理设置线程数:CPU密集型任务线程数建议设置为
CPU核心数+1,IO密集型任务线程数可以设置为CPU核心数*2; - 使用线程池替代直接创建线程:减少线程创建和销毁的开销,提高资源利用率;
- 注意线程异常处理:线程中的异常不会抛出到主线程,需要通过
UncaughtExceptionHandler捕获处理; - 避免共享可变状态:尽量使用局部变量或不可变对象,减少线程同步的复杂性。
通过以上方式,Java开发者可以灵活地开辟和管理多线程,充分利用多核CPU的性能优势,同时通过同步机制和高级工具保证程序的稳定性和高效性。