Java线程

当然可以!Java 中的线程是并发编程的核心,掌握好线程相关的知识对于开发高性能、高并发的应用程序非常重要。下面我将从 基础概念高级应用 系统地为你讲解 Java 中线程相关的全部知识。


🧵 Java 线程详解(完整版)


一、基本概念

1. 什么是线程?

  • 线程(Thread)是操作系统调度的最小单元。
  • 每个进程至少有一个主线程,Java 程序本身就是多线程运行的。
  • Java 中线程是轻量级的进程,多个线程共享同一个进程的资源(如堆内存),但拥有自己的栈空间。

2. 进程 vs 线程

对比项 进程 线程
资源开销 开销大(独立内存空间) 开销小(共享进程资源)
通信 需要 IPC(进程间通信)机制 共享内存,可以直接访问变量
调度 操作系统调度 JVM 和操作系统共同调度
安全性 相互隔离,安全性高 不隔离,一个线程出错可能影响整个进程

二、创建线程的方式

1. 继承 Thread

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread is running");
    }
}

// 使用
MyThread t = new MyThread();
t.start(); // 启动线程

2. 实现 Runnable 接口(推荐)

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable is running");
    }
}

// 使用
Thread t = new Thread(new MyRunnable());
t.start();

✅ 优势:Java不支持多继承,使用 Runnable 更灵活。

3. 使用 Lambda 表达式(Java 8+)

java 复制代码
Thread thread = new Thread(() -> {
    System.out.println("Lambda thread running");
});
thread.start();

4. 使用 CallableFutureTask(可返回结果)

java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

Callable<String> task = () -> {
    return "Result from Callable";
};

FutureTask<String> futureTask = new FutureTask<>(task);
new Thread(futureTask).start();

String result = futureTask.get(); // 获取线程执行结果
System.out.println(result);

一般来说,创建线程有很多种方式,例如继承Thread类、实现Runnable接口、实现Callable接口、使用线程池、使用CompletableFuture类等等。不过,这些方式其实并没有真正创建出线程。准确点来说,这些都属于是在 Java 代码中使用多线程的方法。严格来说,Java 就只有一种方式可以创建线程,那就是通过new Thread().start()创建。不管是哪种方式,最终还是依赖于new Thread().start()


三、线程状态和生命周期

Java 线程有以下几种状态(定义在 Thread.State 枚举中):

状态 描述
NEW 线程被创建但尚未启动
RUNNABLE 正在 JVM 中执行,可能正在等待操作系统资源(如 CPU)
BLOCKED 因为竞争锁而阻塞
WAITING 无限期等待另一个线程执行特定动作
TIMED_WAITING 在指定时间内等待
TERMINATED 线程已结束

由上图可以看出:线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。在操作系统层面,线程有 READY 和 RUNNING 状态 ;而在 JVM 层面,只能看到 RUNNABLE 状态 (图源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。为什么 JVM 没有区分这两种状态呢? (摘自:Java 线程运行怎么有第六种状态? - Dawell 的回答 ) 现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的"时间分片(time quantum or time slice)"方式进行抢占式(preemptive)轮转调度(round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。

当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。

TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。

当线程进入 synchronized 方法/块或者调用 wait 后(被 notify)重新进入 synchronized 方法/块,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。

线程在执行完了 run()方法之后将会进入到 TERMINATED(终止) 状态。

四、线程常用方法

方法名 功能说明
start() 启动线程,调用其 run() 方法
run() 线程体,需重写或实现
sleep(long millis) 让当前线程休眠一段时间
join() 等待该线程终止
yield() 提示调度器当前线程愿意让出CPU时间片
interrupt() 打断线程(设置中断标志位)
isInterrupted() 检查线程是否被打断
getName(), setName() 获取/设置线程名称
setPriority(int) 设置线程优先级(1~10)
setDaemon(boolean) 设置为守护线程(后台线程)

五、线程同步与并发控制

1. synchronized 关键字

用于保证同一时刻只有一个线程能进入临界区代码。

修饰方法
java 复制代码
public synchronized void method() {
    // 同步方法
}
修饰代码块
java 复制代码
synchronized (this) {
    // 同步代码块
}

2. Lock 接口(更灵活)

常见实现类:ReentrantLock

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

Lock lock = new ReentrantLock();

lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}

3. volatile 关键字

用于保证变量的可见性和禁止指令重排序,适用于状态标志等简单场景。

java 复制代码
private volatile boolean flag = true;

4. 原子类(AtomicXXX)

AtomicInteger, AtomicBoolean 等,提供原子操作,避免加锁。

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

六、线程池

为什么要使用线程池?

  • 减少线程创建销毁的开销
  • 控制最大并发数
  • 提升系统响应速度

Java 提供的线程池工具类:Executors

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

ExecutorService executor = Executors.newFixedThreadPool(5);

executor.submit(() -> {
    System.out.println("Task executed by thread pool");
});

executor.shutdown(); // 关闭线程池

常见线程池类型:

线程池类型 特点
newFixedThreadPool 固定大小线程池,重复利用线程
newCachedThreadPool 可缓存线程池,自动回收空闲线程
newSingleThreadExecutor 单线程池,顺序执行任务
newScheduledThreadPool 支持定时和周期性任务的线程池

七、线程通信

1. wait / notify / notifyAll

必须在 synchronized 块中使用:

java 复制代码
synchronized (obj) {
    obj.wait(); // 当前线程释放锁并等待唤醒
    obj.notify(); // 唤醒一个等待线程
    obj.notifyAll(); // 唤醒所有等待线程
}

2. Condition(配合 Lock 使用)

java 复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await(); // 等待
    condition.signal(); // 唤醒
} finally {
    lock.unlock();
}

八、线程安全问题与解决方案

问题 解决方案
数据竞争(Race Condition) 加锁(synchronized、Lock)、原子类、volatile
内存可见性问题 volatile、synchronized、final关键字
活锁、死锁、饥饿 设计时规避、避免嵌套锁、公平锁等

九、守护线程(Daemon Thread)

  • 是一种"后台线程",不影响JVM退出。
  • 主线程结束后,只要还有非守护线程在运行,JVM就不会退出。
java 复制代码
Thread daemonThread = new Thread(() -> {
    while (true) {
        System.out.println("Daemon thread is running...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            break;
        }
    }
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();

十、线程组(ThreadGroup)

用于对一组线程进行统一管理(如监控、中断等)。

java 复制代码
ThreadGroup group = new ThreadGroup("MyGroup");

Thread t1 = new Thread(group, () -> {
    System.out.println("In thread group");
});
t1.start();

group.interrupt(); // 中断组内所有线程

十一、Fork/Join 框架(Java 7+)

适用于"分治"算法(如归并排序、并行计算)。

java 复制代码
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

class SumTask extends RecursiveTask<Integer> {
    private final int[] array;
    private final int start, end;

    public SumTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - start <= 10) {
            int sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        } else {
            int mid = (start + end) / 2;
            SumTask left = new SumTask(array, start, mid);
            SumTask right = new SumTask(array, mid, end);
            left.fork();
            right.fork();
            return left.join() + right.join();
        }
    }
}

// 使用
int[] data = new int[1000];
ForkJoinPool pool = new ForkJoinPool();
int result = pool.invoke(new SumTask(data, 0, data.length));

十二、CompletableFuture(Java 8+)

用于异步编程,支持链式调用和组合操作。

java 复制代码
import java.util.concurrent.CompletableFuture;

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return "Hello";
});

future.thenApply(s -> s + " World")
       .thenAccept(System.out::println);

十三、线程局部变量(ThreadLocal)

每个线程拥有自己的副本,互不干扰。

java 复制代码
ThreadLocal<Integer> local = new ThreadLocal<>();
local.set(100);
System.out.println(local.get()); // 每个线程获取自己的值

十四、实战建议

场景 建议
多线程更新共享变量 使用 synchronizedReentrantLockAtomicXXX
异步处理任务 使用 ExecutorServiceCompletableFuture
并发集合操作 使用 ConcurrentHashMapCopyOnWriteArrayList
简单状态标志 使用 volatile
高性能并发读写 使用 ReadWriteLock
防止死锁 避免嵌套锁,按固定顺序加锁
线程复用 使用线程池代替频繁新建线程

相关推荐
AI+程序员在路上几秒前
QTextCodec的功能及其在Qt5及Qt6中的演变
开发语言·c++·qt
xingshanchang9 分钟前
Matlab的命令行窗口内容的记录-利用diary记录日志/保存命令窗口输出
开发语言·matlab
Risehuxyc9 分钟前
C++卸载了会影响电脑正常使用吗?解析C++运行库的作用与卸载后果
开发语言·c++
AI视觉网奇15 分钟前
git 访问 github
运维·开发语言·docker
不知道叫什么呀22 分钟前
【C】vector和array的区别
java·c语言·开发语言·aigc
liulilittle42 分钟前
.NET ExpandoObject 技术原理解析
开发语言·网络·windows·c#·.net·net·动态编程
wan_da_ren1 小时前
JVM监控及诊断工具-GUI篇
java·开发语言·jvm·后端
委婉待续1 小时前
计算机网络通信的相关知识总结
开发语言·网络
cui_hao_nan1 小时前
JAVA并发——什么是Java的原子性、可见性和有序性
java·开发语言
best_virtuoso1 小时前
JAVA JVM垃圾收集
java·开发语言·jvm