在Java编程中,线程是实现并发编程的基础,它允许程序在同一时间内执行多个任务,从而提高系统资源的利用率和程序的响应速度,理解如何在Java中正确创建和管理线程,是掌握并发编程的关键,本文将从线程的基本概念出发,详细介绍Java中创建线程的三种核心方式,深入探讨线程的生命周期、常用方法、同步机制,以及线程池的高效使用,最后总结线程开发的最佳实践,帮助读者构建系统化的并发编程知识体系。

线程的基本概念与Java内存模型
线程是操作系统进行调度的最小单位,是进程内的执行单元,与进程相比,线程共享进程的内存空间(如堆内存、方法区),拥有独立的程序计数器、虚拟机栈和本地方法栈,这使得线程间的通信成本更低,但同时也需要处理线程安全问题。
Java内存模型(JMM)定义了线程与主内存之间的抽象关系,通过主内存与工作内存的交互(读取、写入、同步等)来保证多线程环境下的可见性和有序性,可见性指一个线程对共享变量的修改能及时被其他线程感知,有序性则指指令的执行顺序按照程序代码的先后顺序执行(在不影响单线程执行结果的前提下),理解JMM有助于后续深入分析线程同步机制。
创建线程的三种核心方式
Java中创建线程主要有三种方式,每种方式都有其适用场景和特点,开发者需根据实际需求选择。
继承Thread类
Thread类是Java.lang包中定义的线程类,通过继承Thread类并重写其run()方法,即可创建一个线程实例,调用start()方法会启动线程,JVM会自动调用run()方法执行线程任务。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行中:" + Thread.currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
特点:实现简单,但Java不支持多继承,若继承Thread类则无法再继承其他父类,灵活性较低。
实现Runnable接口
Runnable接口是一个函数式接口,定义了一个无返回值的run()方法,通过实现Runnable接口,将线程任务逻辑封装在run()方法中,然后将Runnable实例传递给Thread类构造方法,即可创建线程。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行中:" + Thread.currentThread().getName());
}
}
public class RunnableDemo {
public static void main(String[] args) {
Runnable task = new MyRunnable();
Thread thread = new Thread(task);
thread.start();
}
}
特点:避免了单继承的限制,且Runnable任务可以与Thread类分离,更适合线程池管理(线程池可执行Runnable任务)。
实现Callable接口与Future
Runnable的run()方法无法返回结果或抛出受检异常,而Callable接口允许线程任务返回结果(通过泛型指定返回类型)并抛出异常,结合Future接口,可以异步获取线程执行结果。

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 <= 100; 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();
// 主线程继续执行其他任务
System.out.println("主线程执行中...");
// 异步获取线程结果(若线程未完成会阻塞)
Integer result = futureTask.get();
System.out.println("线程计算结果:" + result);
}
}
特点:支持返回结果和异常处理,适合需要异步获取计算结果的场景,但实现相对复杂。
线程的生命周期与状态转换
Java线程具有明确的生命周期,包含六个状态(Java 6之前为五种状态),通过Thread.State枚举类定义:
- NEW(新建):线程被创建但未启动,调用start()方法之前的状态。
- RUNNABLE(可运行):线程调用start()后,进入就绪状态,等待CPU调度执行(包括正在执行的状态)。
- BLOCKED(阻塞):线程等待获取锁(如进入synchronized代码块)时,进入阻塞状态,直到获取锁后进入RUNNABLE状态。
- WAITING(等待):线程等待其他线程显式唤醒(如调用wait()、join()、LockSupport.park()),进入无限等待状态,需通过notify()、notifyAll()或unpark()唤醒。
- TIMED_WAITING(超时等待):线程等待指定时间后自动唤醒(如调用sleep(long millis)、wait(long timeout)、join(long millis)),或在等待期间被唤醒。
- TERMINATED(终止):线程执行完成(run()或call()方法结束)、因异常退出或被stop()方法强制终止(已废弃)后的状态。
状态转换的核心触发条件包括:start()启动线程、CPU调度、锁竞争、等待/唤醒方法调用、线程任务结束等,理解状态转换有助于排查线程“假死”、阻塞等问题。
线程的常用方法与协作机制
Java提供了丰富的线程方法,用于控制线程的执行顺序、状态和协作,以下是核心方法的说明:
- start():启动线程,使线程进入RUNNABLE状态,由JVM调用run()方法,注意:不可重复调用,否则会抛出IllegalThreadStateException。
- run():线程任务执行体,需重写(继承Thread或实现Runnable/Callable),直接调用run()方法不会启动新线程,而是在当前线程执行。
- sleep(long millis):静态方法,让当前线程休眠指定毫秒数,进入TIMED_WAITING状态,不释放锁(若持有锁)。
- join():等待调用join()的线程执行完成后,当前线程继续执行,可设置超时时间(join(long millis)),超时后不再等待。
- yield():静态方法,提示当前线程让出CPU,使同优先级线程有机会执行,但具体是否让出由JVM决定。
- interrupt():中断线程,设置线程的中断状态(不会强制终止线程),线程可通过isInterrupted()检查中断状态,或通过InterruptedException捕获中断信号(如sleep()、wait()等方法被中断时会抛出该异常并清除中断状态)。
- currentThread():静态方法,返回当前正在执行的线程对象。
协作场景示例:通过wait()和notify()实现线程间通信,生产者-消费者模型中,生产者生产数据后调用notify()唤醒消费者,消费者获取数据后调用wait()等待:
class SharedResource {
private int data = 0;
private boolean isEmpty = true;
public synchronized void produce(int value) throws InterruptedException {
while (!isEmpty) { // 避免虚假唤醒,使用while循环判断
wait();
}
data = value;
isEmpty = false;
System.out.println("生产数据:" + data);
notify(); // 唤醒消费者
}
public synchronized int consume() throws InterruptedException {
while (isEmpty) {
wait();
}
int result = data;
isEmpty = true;
System.out.println("消费数据:" + result);
notify(); // 唤醒生产者
return result;
}
}
线程安全与同步机制
当多个线程同时访问共享资源(如全局变量、静态变量、共享对象)时,可能会导致数据不一致,即线程安全问题,解决线程安全的核心是保证操作的“原子性”(不可中断)、“可见性”和“有序性”,Java主要通过以下机制实现:
synchronized关键字
synchronized是Java内置的同步机制,通过锁保证同一时间只有一个线程访问共享资源,可修饰方法(实例方法或静态方法)或代码块:
- 修饰方法:默认锁对象为当前实例对象(静态方法为类对象),锁粒度较大。
- 修饰代码块:可指定锁对象(如this、类对象或任意对象),锁粒度更细,灵活性更高。
class Counter {
private int count = 0;
public synchronized void increment() { // 同步方法
count++;
}
public void decrement() { // 同步代码块
synchronized (this) {
count--;
}
}
}
特点:使用简单,JVM自动管理锁的获取和释放,但不可中断、锁粒度控制不灵活,可能导致死锁。

volatile关键字
volatile修饰的变量具有可见性(每次读取从主内存获取,写入后同步到主内存)和禁止指令重排序(保证有序性),但不保证原子性,适用于一个线程写、多个线程读的场景(如状态标志位)。
class Flag {
private volatile boolean isRunning = true;
public void stop() {
isRunning = false;
}
public void run() {
while (isRunning) {
// 执行任务
}
System.out.println("线程停止");
}
}
Lock接口与ReentrantLock
java.util.concurrent.locks.Lock接口提供了更灵活的锁机制,相比synchronized,支持可中断锁(lockInterruptibly())、尝试获取锁(tryLock())、公平锁(公平锁按请求顺序获取锁)等功能,ReentrantLock是Lock接口的常用实现类,需手动释放锁(在finally块中调用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(); // 确保锁释放
}
}
}
原子类
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(); // 原子递增
}
}
线程池:高效管理线程资源
频繁创建和销毁线程会消耗大量系统资源(如内存、CPU时间),线程池通过复用已创建的线程,降低资源开销,提高系统稳定性,Java提供了Executor框架,核心接口包括Executor(执行任务)、ExecutorService(管理生命周期)、ScheduledExecutorService(定时任务)。
线程池的核心参数
ThreadPoolExecutor是线程池的核心实现类,其构造方法包含以下关键参数:
- corePoolSize:核心线程数,线程池长期保持的线程数量(即使空闲)。
- maximumPoolSize:最大线程数,线程池允许的最大线程数量(核心线程 + 非核心线程)。
- workQueue:任务队列,用于存储等待执行的任务(如ArrayBlockingQueue、LinkedBlockingQueue)。
- keepAliveTime:非核心线程的空闲存活时间,超过时间后非核心线程会被回收。
- threadFactory:线程工厂,用于创建线程(可设置线程名称、优先级等)。
- handler:拒绝策略,当线程数和队列都满时,对新任务的处理方式(如AbortPolicy抛出异常、CallerRunsPolicy由调用线程执行任务)。
创建线程池的方式
- 通过Executors工厂类:提供预配置的线程池,如newFixedThreadPool(固定大小线程池)、newCachedThreadPool(可缓存线程池,动态扩容)、newScheduledThreadPool(定时任务线程池)。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 核心线程数=最大线程数=5
- 通过ThreadPoolExecutor手动创建:更灵活地控制线程池参数,避免资源耗尽风险(如FixedThreadPool和CachedThreadPool可能导致OOM)。
ThreadPoolExecutor customPool = new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );
线程池的使用注意事项
- 合理设置参数:根据任务类型(CPU密集型、IO密集型)调整线程池大小(CPU密集型:核心线程数=CPU核心数+1;IO密集型:核心线程数=CPU核心数*2)。
- 避免任务堆积:选择合适的任务队列(如有界队列防止OOM),并配置合理的拒绝策略。
- 关闭线程池:调用shutdown()(平滑关闭,不再接受新任务,执行已提交任务)或shutdownNow()(立即关闭,尝试中断正在执行的任务),并确认所有任务已完成。
线程使用的最佳实践
- 避免使用stop()和destroy():stop()会强制终止线程,可能导致资源未释放;destroy()已废弃,无法正常工作,应通过中断标志(volatile变量)或协作机制让线程自然结束。
- 谨慎使用synchronized:尽量缩小同步范围(使用同步代码块替代同步方法),避免锁嵌套(可能导致死锁),优先考虑ReentrantLock或原子类。
- 处理线程中断:在长时间运行的任务中,定期检查中断状态(isInterrupted()),或在捕获InterruptedException后恢复中断状态(Thread.currentThread().interrupt()),确保上层调用能感知中断。
- 优先使用线程池:避免手动创建线程,除非有特殊需求(如需要定制线程属性),线程池应作为单例或通过依赖注入管理,避免重复创建。
- 注意可见性:共享变量尽量使用volatile修饰,或通过同步机制保证可见性,避免因缓存导致的数据不一致。
Java线程是并发编程的核心工具,掌握线程的创建、生命周期、同步机制和线程池管理,是构建高性能、高并发应用的基础,开发者需根据实际场景选择合适的线程创建方式,合理使用同步机制保证线程安全,并通过线程池优化资源利用,遵循最佳实践,避免常见的并发问题(如死锁、数据不一致),才能充分发挥线程的优势,编写出稳定、高效的并发程序。