线程是Java并发编程的核心概念,它允许程序在同一个进程中执行多个任务,从而提高CPU利用率和程序响应速度,理解如何在Java中正确创建、管理和同步线程,是开发高性能、高并发应用的基础,本文将从线程的基础概念出发,详细讲解Java中线程的创建方式、生命周期、常用方法、同步机制以及线程池的使用,帮助读者全面掌握Java线程编程。

线程的基础概念
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源,但每个线程拥有独立的程序计数器、栈和局部变量,与进程相比,线程的创建和销毁开销更小,切换效率更高,因此适合处理高并发任务。
在Java中,线程的调度由Java虚拟机(JVM)和操作系统共同完成,JVM通过线程调度器分配CPU时间片,线程通过竞争时间片来执行任务,Java中的线程实现了java.lang.Thread类,开发者可以通过继承或实现接口来创建自定义线程。
Java中创建线程的三种方式
Java提供了多种创建线程的方式,每种方式都有其适用场景和优缺点,开发者可以根据需求选择合适的方法。
继承Thread类
最直接的创建线程方式是继承Thread类并重写其run()方法。run()方法中定义了线程要执行的任务,通过调用线程的start()方法启动线程。
示例代码:
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 thread = new MyThread();
thread.start(); // 启动线程
}
}
优缺点:
- 优点:实现简单,直接使用
this即可访问当前线程对象。 - 缺点:Java单继承限制,若类已继承其他父类则无法再继承
Thread类;线程与任务耦合,不利于代码复用。
实现Runnable接口
更灵活的方式是实现Runnable接口,将线程任务与线程本身分离。Runnable接口只有一个run()方法,实现该接口后,将实例传递给Thread构造函数即可创建线程。
示例代码:
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) {
Runnable task = new MyRunnable();
Thread thread = new Thread(task);
thread.start();
}
}
优缺点:
- 优点:避免单继承限制,线程任务可独立于线程类,实现“任务与执行分离”;多个线程可共享同一个
Runnable实例,适合处理共享资源场景。 - 缺点:
Runnable接口没有返回值,无法直接获取线程执行结果。
实现Callable接口
当需要获取线程执行结果时,可以使用Callable接口。Callable接口是Java 5引入的,类似于Runnable,但其call()方法支持返回结果和抛出异常。

示例代码:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
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 Exception {
Callable<Integer> task = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
// 获取线程执行结果(会阻塞,直到线程完成)
Integer result = futureTask.get();
System.out.println("计算结果: " + result);
}
}
优缺点:
- 优点:支持返回结果,可抛出异常,适合需要线程返回值的场景。
- 缺点:使用相对复杂,需配合
FutureTask类获取结果。
线程的生命周期与状态
Java线程的生命周期包括多个状态,通过Thread.State枚举定义,共有6种状态:
- NEW(新建):线程被创建但未启动,调用
start()方法之前的状态。 - RUNNABLE(可运行):线程已启动,正在等待CPU时间片或正在执行,Java将操作系统层面的“就绪”和“运行”状态统一为
RUNNABLE。 - BLOCKED(阻塞):线程等待获取锁(如
synchronized锁),进入阻塞状态。 - WAITING(等待):线程等待其他线程显式唤醒(如调用
wait()、join()、LockSupport.park())。 - TIMED_WAITING(超时等待):等待指定时间后自动唤醒(如调用
sleep(long millis)、wait(long timeout))。 - TERMINATED(终止):线程执行完成或因异常退出,生命周期结束。
状态转换由JVM和操作系统自动管理,开发者可通过Thread.getState()方法获取线程当前状态。
线程的常用方法
线程的常用方法用于控制线程的行为,以下是核心方法的说明:
- start():启动线程,JVM会调用线程的
run()方法,注意:直接调用run()方法不会创建新线程,仅在当前线程执行任务。 - run():线程执行的任务方法,需由开发者重写(继承
Thread或实现Runnable/Callable时)。 - sleep(long millis):让当前线程休眠指定毫秒数,进入
TIMED_WAITING状态,不释放锁。 - yield():让当前线程主动让出CPU时间片,进入
RUNNABLE状态,让同优先级线程有机会执行。 - join():等待当前线程执行完成后再执行后续代码。
thread.join()会使主线程阻塞,直到thread线程结束。 - interrupt():中断线程,通过设置线程的中断标志实现,若线程处于阻塞状态(如
sleep()),会抛出InterruptedException并清除中断标志。 - currentThread():静态方法,返回当前正在执行的线程对象。
线程同步机制
多线程环境下,共享资源的访问可能导致数据不一致问题(如脏读、丢失更新),Java提供了多种同步机制来保证线程安全:
synchronized关键字
synchronized是Java内置的同步机制,通过锁机制实现互斥访问,它可以修饰方法、代码块或静态方法:
- 修饰实例方法:锁为当前对象实例(
this),同一时间只有一个线程能访问该对象的同步方法。 - 修饰静态方法:锁为当前类的
Class对象,同一时间只有一个线程能访问该类的所有同步静态方法。 - 修饰代码块:可指定锁对象,灵活性更高,可缩小同步范围,减少锁竞争。
示例(同步代码块):
class Counter {
private int count = 0;
private final Object lock = new Object(); // 锁对象
public void increment() {
synchronized (lock) { // 同步代码块
count++;
}
}
}
Lock接口
java.util.concurrent.locks.Lock接口提供了更灵活的锁机制,支持公平锁、非公平锁、可中断锁等,常用实现类为ReentrantLock,它比synchronized更强大,但需手动释放锁(通过unlock()方法)。
示例:

import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 释放锁(必须放在finally中)
}
}
}
volatile关键字
volatile用于修饰变量,确保多线程环境下变量的可见性(一个线程修改后,其他线程立即可见)和禁止指令重排序,但它不保证原子性,仅适用于“一个线程写,多个线程读”的场景。
示例:
class Flag {
private volatile boolean isRunning = true; // 修改后对其他线程可见
public void stop() {
isRunning = false;
}
public void run() {
while (isRunning) {
// 执行任务
}
}
}
原子类
java.util.concurrent.atomic包下的原子类(如AtomicInteger、AtomicLong)通过CAS(Compare-And-Swap)操作实现原子性,适合高并发场景下的简单数据操作。
示例:
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子递增
}
}
线程池的使用
频繁创建和销毁线程会带来性能开销,线程池通过复用线程、管理线程数量,有效解决了这一问题,Java提供了java.util.concurrent.Executor框架,核心类为ThreadPoolExecutor。
ThreadPoolExecutor构造参数
ThreadPoolExecutor的构造方法包含7个核心参数:
- corePoolSize:核心线程数,线程池长期保持的线程数量。
- maximumPoolSize:最大线程数,线程池允许的最大线程数。
- keepAliveTime:空闲线程存活时间,超过该时间的空闲线程会被回收(核心线程除外)。
- unit:存活时间单位(如
TimeUnit.SECONDS)。 - workQueue:工作队列,用于存储待执行任务(常用
ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue)。 - threadFactory:线程工厂,用于创建线程(可自定义线程名称、优先级等)。
- handler:拒绝策略,当线程数和队列都满时,对新任务的处理方式(如
AbortPolicy抛出异常、CallerRunsPolicy由调用线程执行)。
创建线程池的方式
推荐通过ThreadPoolExecutor手动创建线程池,避免使用Executors工具类(如newFixedThreadPool、newCachedThreadPool可能因队列无界导致OOM)。
示例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 工作队列(容量100)
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is running");
});
}
pool.shutdown(); // 关闭线程池
}
}
线程编程最佳实践
- 避免锁竞争:尽量缩小同步范围,使用细粒度锁或无锁数据结构(如
ConcurrentHashMap)。 - 合理设置线程池参数:根据任务类型(CPU密集型、IO密集型)调整核心线程数和工作队列。
- 处理线程异常:线程中的异常不会抛出到调用方,需通过
Thread.setUncaughtExceptionHandler捕获处理。 - 避免阻塞操作:线程中尽量不执行耗时IO操作,可使用异步任务或专用IO线程池。
Java线程编程是并发开发的基础,掌握线程的创建、生命周期、同步机制和线程池使用,能够帮助开发者编写高效、稳定的并发程序,在实际开发中,需根据场景选择合适的线程创建方式,合理使用同步机制保证线程安全,并通过线程池优化资源利用,注意避免常见的并发问题(如死锁、内存泄漏),遵循最佳实践,才能充分发挥多线程的优势。















