怀化市网站建设_网站建设公司_后端工程师_seo优化
2025/12/21 23:33:27 网站建设 项目流程

1. 理解线程:多任务执行的基石

1.1 什么是线程?

在现代操作系统中,进程是资源分配的基本单位,而线程是CPU调度的最小单位。可以把进程想象成一家公司,线程就是公司里的员工。

/**

* 演示Java程序天生就是多线程程序

* 即使最简单的main方法也会启动多个系统线程

*/

public class MultiThread {

public static void main(String[] args) {

// 获取Java线程管理MXBean

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息

ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);

// 遍历线程信息

System.out.println("=== Java程序启动的线程列表 ===");

for (ThreadInfo threadInfo : threadInfos) {

System.out.println("[" + threadInfo.getThreadId() + "] " +

threadInfo.getThreadName());

}

}

}

输出示例:

=== Java程序启动的线程列表 ===

[4] Signal Dispatcher // 分发处理发送给JVM信号的线程

[3] Finalizer // 调用对象finalize方法的线程

[2] Reference Handler // 清除Reference的线程

[1] main // main线程,用户程序入口

1.2 为什么需要多线程?

三大核心优势:

充分利用多核处理器 - 避免CPU资源闲置

提升响应速度 - 后台任务不阻塞用户操作

更好的编程模型 - Java提供一致的多线程API

1.3 线程状态生命周期

新建(NEW) → 可运行(RUNNABLE) → 运行中

超时等待(TIMED_WAITING) ← 等待(WAITING) ← 阻塞(BLOCKED)

终止(TERMINATED)

2. 线程的启动与安全终止

2.1 正确启动线程

/**

* 线程启动最佳实践示例

* 重点:设置有意义的线程名称,合理设置守护线程标志

*/

public class ThreadStartExample {

public static void main(String[] args) {

// 推荐:为线程设置有意义的名称,便于问题排查

Thread worker = new Thread(new Task(), "Data-Processor-1");

worker.setDaemon(false); // 明确设置是否为守护线程

worker.start(); // 正确启动方式,不要直接调用run()

System.out.println("主线程继续执行,不会等待worker线程");

}

static class Task implements Runnable {

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + " 开始执行");

try {

// 模拟工作任务

Thread.sleep(1000);

} catch (InterruptedException e) {

System.out.println("任务被中断");

}

System.out.println(Thread.currentThread().getName() + " 执行完成");

}

}

}

2.2 安全终止线程的两种方式

方式一:使用中断机制

/**

* 使用中断机制安全终止线程

* 重点:理解中断异常处理的最佳实践

*/

public class InterruptExample {

public static void main(String[] args) throws InterruptedException {

Thread worker = new Thread(new InterruptibleTask(), "Interruptible-Worker");

worker.start();

// 主线程等待2秒后中断工作线程

TimeUnit.SECONDS.sleep(2);

System.out.println("主线程发送中断信号");

worker.interrupt(); // 发送中断信号

// 等待工作线程完全退出

worker.join();

System.out.println("工作线程已安全退出");

}

static class InterruptibleTask implements Runnable {

@Override

public void run() {

while (!Thread.currentThread().isInterrupted()) {

try {

// 模拟工作 - 这里可能抛出InterruptedException

System.out.println("Working...");

TimeUnit.MILLISECONDS.sleep(500);

} catch (InterruptedException e) {

/**

* 关键理解点:为什么需要重新设置中断状态?

*

* 当线程在阻塞状态(如sleep、wait、join)时被中断,

* Java会做两件事:

* 1. 抛出InterruptedException

* 2. 清除线程的中断状态(设为false)

*

* 这导致循环条件 !Thread.currentThread().isInterrupted()

* 会继续为true,线程无法退出。

*

* 因此我们需要在捕获异常后重新设置中断状态,

* 这样循环条件就能检测到中断,安全退出。

*/

System.out.println("捕获到中断异常,重新设置中断状态");

Thread.currentThread().interrupt(); // 重新设置中断标志

}

}

System.out.println("线程安全退出,中断状态: " +

Thread.currentThread().isInterrupted());

}

}

}

方式二:使用标志位

/**

* 使用volatile标志位安全终止线程

* 适用于没有阻塞调用或需要更复杂退出逻辑的场景

*/

public class FlagShutdownExample {

// volatile保证可见性,确保所有线程看到最新的值

private volatile boolean running = true;

private final Thread workerThread;

public FlagShutdownExample() {

this.workerThread = new Thread(this::doWork, "Flag-Controlled-Worker");

}

public void start() {

workerThread.start();

}

/**

* 优雅停止工作线程

*/

public void stop() {

System.out.println("请求停止工作线程");

running = false;

// 同时发送中断,处理可能存在的阻塞情况

workerThread.interrupt();

}

/**

* 工作线程的主循环

* 同时检查标志位和中断状态,提供双重保障

*/

private void doWork() {

try {

while (running && !Thread.currentThread().isInterrupted()) {

// 执行工作任务

processData();

}

} finally {

// 无论何种方式退出,都执行清理工作

cleanup();

}

System.out.println("工作线程已安全退出");

}

private void processData() {

try {

// 模拟数据处理

System.out.println("处理数据中...");

Thread.sleep(300);

} catch (InterruptedException e) {

System.out.println("处理数据时被中断");

// 收到中断,但可能还想继续处理,所以不重新设置中断

// 让循环条件来检查running标志

}

}

private void cleanup() {

System.out.println("执行资源清理工作...");

// 关闭文件、数据库连接等资源

}

public static void main(String[] args) throws InterruptedException {

FlagShutdownExample example = new FlagShutdownExample();

example.start();

// 运行3秒后停止

Thread.sleep(3000);

example.stop();

// 等待工作线程退出

example.workerThread.join();

}

}

3. 线程间通信:协作的艺术

3.1 volatile关键字:共享状态可见性

/**

* volatile关键字示例

* 保证多线程间的可见性,但不保证原子性

*/

public class VolatileExample {

// volatile确保shutdownRequested的修改对所有线程立即可见

private volatile boolean shutdownRequested = false;

private int operationCount = 0; // 非volatile,不保证可见性

public void shutdown() {

shutdownRequested = true; // 所有线程立即可见

System.out.println("关闭请求已设置");

}

public void doWork() {

while (!shutdownRequested) {

// 正常工作循环

operationCount++; // 非原子操作,可能有问题

try {

Thread.sleep(100);

} catch (InterruptedException e) {

System.out.println("工作被中断");

Thread.currentThread().interrupt();

break;

}

}

System.out.println("工作线程退出,操作次数: " + operationCount);

}

}

3.2 synchronized关键字:互斥访问

/**

* synchronized关键字示例

* 保证原子性和可见性,但可能影响性能

*/

public class SynchronizedCounter {

private int count = 0;

/**

* 同步方法 - 锁对象是当前实例(this)

*/

public synchronized void increment() {

count++; // 原子操作

}

/**

* 同步块 - 可以更细粒度控制锁的范围

*/

public void decrement() {

// 只同步关键部分,减少锁持有时间

synchronized (this) {

count--;

}

// 这里可以执行非同步操作

}

/**

* 同步的get方法,保证看到最新值

*/

public synchronized int getCount() {

return count;

}

/**

* 静态同步方法 - 锁对象是类的Class对象

*/

public static synchronized void staticMethod() {

// 静态同步方法使用Class对象作为锁

}

}

3.3 等待/通知机制:经典生产者-消费者模式

/**

* 生产者-消费者模式示例

* 演示wait/notify机制的正确使用

*/

public class WaitNotifyExample {

private final Object lock = new Object(); // 共享锁对象

private final Queue<String> queue = new LinkedList<>();

private final int MAX_SIZE = 5;

/**

* 生产者方法

*/

public void produce(String data) throws InterruptedException {

synchronized (lock) {

// 必须使用while循环检查条件,避免虚假唤醒

while (queue.size() >= MAX_SIZE) {

System.out.println("队列已满(" + queue.size() + "),生产者等待");

lock.wait(); // 释放锁并等待

}

queue.offer(data);

System.out.println("生产: " + data + ",队列大小: " + queue.size());

// 通知所有等待的消费者

lock.notifyAll();

}

}

/**

* 消费者方法

*/

public String consume() throws InterruptedException {

synchronized (lock) {

// 必须使用while循环检查条件

while (queue.isEmpty()) {

System.out.println("队列为空,消费者等待");

lock.wait(); // 释放锁并等待

}

String data = queue.poll();

System.out.println("消费: " + data + ",队列大小: " + queue.size());

// 通知所有等待的生产者

lock.notifyAll();

return data;

}

}

/**

* 测试生产者消费者模式

*/

public static void main(String[] args) {

WaitNotifyExample example = new WaitNotifyExample();

// 启动生产者线程

Thread producer = new Thread(() -> {

try {

for (int i = 0; i < 10; i++) {

example.produce("Data-" + i);

Thread.sleep(200);

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}, "Producer");

// 启动消费者线程

Thread consumer = new Thread(() -> {

try {

for (int i = 0; i < 10; i++) {

example.consume();

Thread.sleep(300);

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}, "Consumer");

producer.start();

consumer.start();

}

}

等待/通知经典范式:

// 消费者范式 - 永远在循环中调用wait()

synchronized(锁对象) {

while(条件不满足) {

锁对象.wait(); // 等待时会释放锁

}

// 条件满足,处理业务逻辑

}

// 生产者范式

synchronized(锁对象) {

改变条件; // 改变等待条件

锁对象.notifyAll(); // 通知所有等待线程

}

3.4 Thread.join():线程依赖执行

/**

* Thread.join()使用示例

* 实现线程间的顺序执行依赖

*/

public class JoinExample {

public static void main(String[] args) throws InterruptedException {

System.out.println("主线程开始");

Thread previous = Thread.currentThread();

// 创建5个有依赖关系的线程

for (int i = 0; i < 5; i++) {

Thread thread = new Thread(new DependentTask(previous), "Worker-" + i);

thread.start();

previous = thread; // 设置依赖链

}

// 主线程先做一些工作

TimeUnit.SECONDS.sleep(1);

System.out.println(Thread.currentThread().getName() + " 完成初始化工作");

// 等待所有线程完成(实际上由最后一个Worker-4 join主线程)

}

static class DependentTask implements Runnable {

private final Thread dependency; // 依赖的线程

public DependentTask(Thread dependency) {

this.dependency = dependency;

}

@Override

public void run() {

try {

// 等待依赖的线程执行完成

System.out.println(Thread.currentThread().getName() + " 等待 " + dependency.getName());

dependency.join();

// 依赖线程完成后开始自己的工作

System.out.println(Thread.currentThread().getName() + " 开始工作");

TimeUnit.MILLISECONDS.sleep(500); // 模拟工作

System.out.println(Thread.currentThread().getName() + " 完成工作");

} catch (InterruptedException e) {

System.out.println(Thread.currentThread().getName() + " 被中断");

Thread.currentThread().interrupt();

}

}

}

}

3.5 ThreadLocal深入解析:线程局部变量

/**

* ThreadLocal深度解析

* 理解原理、使用场景和内存泄漏防护

*/

public class ThreadLocalExample {

/**

* ThreadLocal基本使用:每个线程独立的SimpleDateFormat

* 避免SimpleDateFormat的线程安全问题

*/

private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER =

ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

/**

* ThreadLocal用于用户上下文传递

* 在Web应用中非常有用,避免在方法参数中传递用户信息

*/

private static final ThreadLocal<UserContext> USER_CONTEXT =

new ThreadLocal<>();

/**

* ThreadLocal用于事务上下文

*/

private static final ThreadLocal<TransactionContext> TRANSACTION_CONTEXT =

new ThreadLocal<>();

/**

* 可继承的ThreadLocal:子线程可以继承父线程的值

*/

private static final InheritableThreadLocal<String> INHERITABLE_CONTEXT =

new InheritableThreadLocal<>();

/**

* 处理用户请求的示例方法

*/

public void processRequest(User user) {

// 设置用户上下文到当前线程

USER_CONTEXT.set(new UserContext(user));

try {

// 使用线程安全的日期格式化

String timestamp = DATE_FORMATTER.get().format(new Date());

System.out.println(Thread.currentThread().getName() +

" - 用户: " + user.getName() + ", 时间: " + timestamp);

// 执行业务逻辑 - 任何方法都可以获取用户上下文,无需传递参数

doBusinessLogic();

} finally {

/**

* 关键:必须清理ThreadLocal,防止内存泄漏!

*

* 原因:

* 1. ThreadLocalMap的key是弱引用,会被GC回收

* 2. 但value是强引用,不会被自动回收

* 3. 如果线程长时间存活(如线程池中的线程),会导致value无法释放

* 4. 调用remove()方法显式清理

*/

USER_CONTEXT.remove();

DATE_FORMATTER.remove(); // 清理所有使用的ThreadLocal

}

}

private void doBusinessLogic() {

// 在任何地方都可以获取用户上下文,无需方法参数传递

UserContext context = USER_CONTEXT.get();

if (context != null) {

System.out.println("执行业务逻辑,用户: " + context.getUser().getName());

}

// 使用线程安全的日期格式化

String now = DATE_FORMATTER.get().format(new Date());

System.out.println("业务执行时间: " + now);

}

/**

* 演示ThreadLocal的内存泄漏问题

*/

public void demonstrateMemoryLeak() {

// 错误的用法:不清理ThreadLocal

ThreadLocal<byte[]> leakyLocal = new ThreadLocal<>();

leakyLocal.set(new byte[1024 * 1024]); // 1MB数据

// 如果没有调用 leakyLocal.remove(), 即使leakyLocal=null,

// 线程的ThreadLocalMap中仍然保留着这个Entry

// 在线程池场景下,线程重用会导致内存不断增长

}

/**

* ThreadLocal最佳实践:使用try-finally确保清理

*/

public void bestPractice(User user) {

USER_CONTEXT.set(new UserContext(user));

try {

// 业务处理

doBusinessLogic();

} finally {

// 确保清理,即使在业务逻辑中发生异常

USER_CONTEXT.remove();

}

}

/**

* 测试多线程环境下的ThreadLocal

*/

public static void main(String[] args) throws InterruptedException {

ThreadLocalExample example = new ThreadLocalExample();

// 创建多个线程,每个线程有独立的ThreadLocal值

Thread[] threads = new Thread[3];

for (int i = 0; i < threads.length; i++) {

final int userId = i;

threads[i] = new Thread(() -> {

User user = new User("User-" + userId);

example.processRequest(user);

}, "Thread-" + i);

threads[i].start();

}

// 等待所有线程完成

for (Thread thread : threads) {

thread.join();

}

System.out.println("所有线程执行完成");

}

// 辅助类定义

static class UserContext {

private final User user;

public UserContext(User user) { this.user = user; }

public User getUser() { return user; }

}

static class User {

private final String name;

public User(String name) { this.name = name; }

public String getName() { return name; }

}

static class TransactionContext {

// 事务相关信息

}

}

/**

* ThreadLocal高级用法:自定义ThreadLocal子类

*/

class AdvancedThreadLocal<T> extends ThreadLocal<T> {

/**

* 初始值 - 当线程第一次调用get()时,如果还没有设置值,会调用此方法

*/

@Override

protected T initialValue() {

System.out.println(Thread.currentThread().getName() + " - 初始化ThreadLocal值");

return null; // 返回默认初始值

}

/**

* 子线程值继承 - 仅对InheritableThreadLocal有效

* 当创建新线程时,可以控制如何从父线程继承值

*/

protected T childValue(T parentValue) {

System.out.println("子线程继承父线程的值: " + parentValue);

return parentValue; // 直接继承,也可以进行转换

}

}

4. 线程应用实例:从理论到实践

4.1 等待超时模式:避免无限期等待

/**

* 等待超时模式实现

* 在等待/通知机制基础上增加超时控制

*/

public class TimeoutWait<T> {

private T result;

/**

* 带超时的获取方法

* @param timeoutMs 超时时间(毫秒)

* @return 结果,超时返回null

*/

public synchronized T get(long timeoutMs) throws InterruptedException {

long endTime = System.currentTimeMillis() + timeoutMs;

long remaining = timeoutMs;

// 循环检查条件和剩余时间

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询