Java多线程核心原理、实战问题
一、引言
互联网高并发业务需高效利用硬件资源,多线程通过任务拆分、并行执行突破单线程性能限制。Java 原生支持多线程,依托JUC工具包简化并发开发,但并发场景易出现线程不安全、死锁等问题。本文聚焦核心知识点,结合代码示例讲解实战用法与避坑方案。
二、Java多线程核心基础
2.1 线程与进程的区别
进程是操作系统资源分配最小单元,独立占用内存、文件资源,进程间隔离、通信成本高;线程是进程内CPU调度最小单元,轻量级进程,共享进程堆、方法区资源,仅栈内存私有,通信效率高、开销小。多线程核心价值:低开销实现任务并行,最大化利用CPU多核。
2.2 Java线程的核心特性
-
抢占式执行:操作系统随机分配CPU时间片,线程执行顺序不可控。
-
资源共享性:线程共享进程公共资源,是并发高效的核心,也是资源竞争的根源。
-
线程独立性:单线程异常不影响整体进程及其他线程。
-
并发三大特性:原子性、可见性、有序性,是线程安全的核心保障。
2.3 线程完整生命周期
Java线程共6种状态,由Thread.State定义,状态转换贯穿线程完整生命周期,核心场景及规则如下:
-
NEW 新建:线程对象初始化完成,未调用start(),未绑定系统线程。
-
RUNNABLE 可运行:调用start()后,就绪等待CPU或正在执行。
-
BLOCKED 阻塞:竞争synchronized锁失败,等待获取锁。
-
WAITING 无限等待:调用wait()、join()等,主动休眠,需手动唤醒。
-
TIMED_WAITING 限时等待:sleep()、wait(long)等,超时自动唤醒。
-
TERMINATED 终止:任务执行完毕或异常退出,线程生命周期结束。
核心规则:线程仅能从NEW→RUNNABLE,不可重复启动;终止状态为最终状态,无法恢复。
三、Java多线程四种实现方式
Java多线程有4种主流实现方式,适配不同业务场景,以下附极简可运行代码示例。
3.1 继承Thread类(基础方式)
继承Thread类,重写run()方法,无返回值、简单易用,受单继承限制。
// 自定义线程类
class MyThread extends Thread {
@Override
public void run() {
// 线程执行任务
System.out.println("Thread线程执行:" + Thread.currentThread().getName());
}
}
// 测试
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
3.2 实现Runnable接口(常用基础方式)
实现Runnable接口,规避单继承限制,任务与线程解耦,无返回值,通用基础方案。
// 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable线程执行:" + Thread.currentThread().getName());
}
}
// 测试
public class RunnableDemo {
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
}
}
3.3 实现Callable接口(带返回值)
JDK1.5新增,支持泛型返回值、可抛异常,通过FutureTask获取异步执行结果。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 实现Callable接口
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable线程执行,计算1+2");
return 3; // 带返回值
}
}
// 测试
public class CallableDemo {
public static void main(String[] args) throws Exception {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
// 获取线程执行结果
System.out.println("执行结果:" + futureTask.get());
}
}
3.4 线程池创建线程(企业级最优方案)
手动创建线程开销大、不可管控,线程池可复用线程、限制并发数、避免OOM,是企业级标准方案。核心类ThreadPoolExecutor,生产环境禁止使用Executors默认线程池。
import java.util.concurrent.*;
// 自定义线程池(企业级推荐)
public class ThreadPoolDemo {
public static void main(String[] args) {
// 初始化线程池七大核心参数
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
10, // 空闲线程超时时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 5; i++) {
int num = i;
threadPool.execute(() ->
System.out.println("线程池执行任务:" + num)
);
}
threadPool.shutdown(); // 关闭线程池
}
}
四、Java并发编程三大核心特性
并发线程安全的核心是保障原子性、可见性、有序性,三大特性失效会导致数据错乱、逻辑异常。
4.1 原子性
原子性:操作不可分割,要么全成功、要么全失败。i++等复合操作非原子操作,多线程下数据不安全。
保障方案:synchronized、ReentrantLock、JUC原子类。示例(AtomicInteger 无锁原子操作):
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo {
// 原子整型,保障自增原子性
private static final AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 10个线程同时自增
for (int i = 0; i < 10; i++) {
new Thread(() -> count.incrementAndGet()).start();
}
Thread.sleep(1000);
System.out.println("最终结果:" + count.get()); // 稳定输出10
}
}
4.2 可见性
可见性:一个线程修改共享变量,其他线程可实时感知。线程私有工作内存与主内存数据不同步,会导致可见性问题。
保障方案:volatile、synchronized、Lock。示例(volatile 保证可见性):
public class VolatileDemo {
// volatile 强制读写主内存,保证可见性
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {}
System.out.println("线程感知flag变更,退出循环");
}).start();
Thread.sleep(500);
flag = false; // 修改共享变量
}
}
4.3 有序性
有序性:代码执行顺序与编写顺序一致。JVM、CPU为优化性能会指令重排序,多线程下引发逻辑错乱。
保障方案:volatile(禁止重排序)、锁机制(保证串行执行)。
五、多线程核心问题与实战解决方案
5.1 线程安全问题
成因:多线程并发操作共享资源,三大特性失效导致数据不一致,是最常见线上并发问题。
核心解决方案:
-
悲观锁(写多读少):synchronized、ReentrantLock,串行执行保证安全。
-
乐观锁(读多写少):CAS无锁机制,JUC原子类实现,性能更高。
-
资源隔离:ThreadLocal实现线程私有资源,彻底规避竞争。
synchronized 线程安全示例:
public class SyncDemo {
private static int count = 0;
// 同步方法,保证原子性、可见性
public static synchronized void add() {
count++;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(SyncDemo::add).start();
}
Thread.sleep(1000);
System.out.println("最终结果:" + count); // 稳定输出10
}
}
5.2 死锁问题
定义:多线程互相持有对方所需锁,循环等待、永久阻塞,导致程序卡死。
四大必要条件:互斥、请求保持、不可剥夺、循环等待(同时满足触发死锁)。
死锁示例(经典双锁嵌套):
public class DeadLockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1:先锁lock1,再锁lock2
new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1获取lock1");
try { Thread.sleep(500); } catch (Exception e) {}
synchronized (lock2) {
System.out.println("线程1获取lock2");
}
}
}).start();
// 线程2:先锁lock2,再锁lock1
new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2获取lock2");
try { Thread.sleep(500); } catch (Exception e) {}
synchronized (lock1) {
System.out.println("线程2获取lock1");
}
}
}).start();
}
}
规避方案:破坏循环等待(统一锁顺序)、一次性申请所有锁、使用tryLock超时锁、杜绝嵌套锁。
5.3 线程活跃性问题
活跃性问题包含死锁、线程饥饿、活锁,均会导致任务无法正常执行:
-
线程饥饿:线程长期抢不到CPU/锁,一直等待。解决:使用公平锁、统一线程优先级。
-
活锁:线程持续运行但无有效业务产出,CPU空转。解决:添加随机重试间隔。
更多推荐
所有评论(0)