42. 多线程编程解析,并发并行艺术
哈喽 大家好!
我是老陈,这节课一起来学习多线程编程 , 多线程就像 “程序的并行流水线”,让多个任务,如数据计算、网络请求、界面响应同时执行,充分利用多核 CPU 资源。
43.1 线程基础:从进程到线程的执行单元进化
Java多线程的核心体系:通过 Thread 类和 Runnable 接口创建线程,用 synchronized、Lock 等机制保证线程安全。
package com.thread.demo;
/**
* 方式一:继承Thread类
* 重写run()方法定义线程执行体
*/
class MyThread extends Thread {
@Override
public void run() {
// 打印当前执行线程的名称(默认由JVM分配或通过setName()设置)
System.out.println("MyThread正在执行 - 当前线程:" + Thread.currentThread().getName());
// 模拟耗时操作,展示线程休眠特性
try {
System.out.println("MyThread休眠100毫秒...");
Thread.sleep(100); // 线程暂停执行100毫秒
} catch (InterruptedException e) {
// 当线程在休眠时被中断,会抛出此异常
System.out.println("MyThread被中断:" + e.getMessage());
Thread.currentThread().interrupt(); // 恢复中断状态,以便上层代码处理
}
System.out.println("MyThread执行完毕");
}
}
/**
* 方式二:实现Runnable接口
* 实现run()方法定义任务逻辑
*/
class MyRunnable implements Runnable {
@Override
public void run() {
// 打印当前执行线程的名称(由Thread实例指定)
System.out.println("MyRunnable任务正在执行 - 当前线程:" + Thread.currentThread().getName());
// 打印线程优先级(继承自启动它的线程,此处为主线程默认优先级5)
System.out.println("当前线程优先级:" + Thread.currentThread().getPriority());
// 模拟重复性任务执行
for (int i = 0; i < 3; i++) {
System.out.println("MyRunnable: " + i);
try {
Thread.sleep(20); // 每次循环暂停20毫秒,展示多线程交替执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
break; // 实际开发中可根据需求决定是否终止任务
}
}
System.out.println("MyRunnable任务执行完毕");
}
}
/**
* 方式三:实现Callable接口
* 实现call()方法,可以返回结果并抛出异常
*/
class MyCallable implements java.util.concurrent.Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallable任务正在执行 - 当前线程:" + Thread.currentThread().getName());
// 模拟耗时计算(如数据库查询、网络请求等)
Thread.sleep(150);
// 返回计算结果(通过FutureTask.get()获取)
return "计算结果:" + (5 + 5);
}
}
/**
* 演示三种创建线程的方式及其特性
* 包含线程优先级设置、状态观察、中断处理等基础操作
*/
public class ThreadBasicFeatures {
public static void main(String[] args) throws InterruptedException {
// 1. 继承Thread类创建线程,创建线程实例并设置优先级
System.out.println("=====1.继承Thread类创建线程 =====");
MyThread thread1 = new MyThread();
thread1.setPriority(7); // 设置线程优先级(1-10,默认5,优先级高的线程可能获得更多CPU时间)
// 启动线程(注意:调用start()才会创建新线程,直接调用run()相当于普通方法调用)
System.out.println("启动thread1...");
thread1.start();
// 主线程短暂等待,确保thread1有机会执行(线程调度由操作系统决定,此处仅增加概率)
Thread.sleep(10);
System.out.println("thread1优先级:" + thread1.getPriority());
// 2.实现Runnable接口创建线程
System.out.println("\n===== 2. 实现Runnable接口创建线程 =====");
// 创建Runnable任务实例(可被多个线程共享)
MyRunnable task = new MyRunnable();
// 将任务包装到线程中并命名(便于调试和日志记录)
Thread thread2 = new Thread(task, "Runnable线程");
// 启动线程
System.out.println("启动thread2...");
thread2.start();
//3. 使用Callable创建有返回值的线程
System.out.println("\n===== 3. 使用Callable创建有返回值的线程 =====");
// 创建Callable任务
MyCallable callableTask = new MyCallable();
// 使用FutureTask包装Callable(FutureTask实现了RunnableFuture接口,可作为Thread任务)
java.util.concurrent.FutureTask<String> futureTask =
new java.util.concurrent.FutureTask<>(callableTask);
// 将FutureTask包装到线程中
Thread thread3 = new Thread(futureTask, "Callable线程");
// 启动线程(执行FutureTask的run()方法,内部调用Callable的call()方法)
System.out.println("启动thread3...");
thread3.start();
try {
// 获取Callable的返回值(这是一个阻塞操作,会等待任务完成)
System.out.println("等待Callable结果...");
String result = futureTask.get(); // 可指定超时参数避免无限等待
System.out.println("Callable返回结果:" + result);
} catch (java.util.concurrent.ExecutionException e) {
// 捕获Callable.call()抛出的异常
System.out.println("执行Callable任务时发生错误:" + e.getCause());
}
//4. 观察线程状态
System.out.println("\n=====4. 观察线程状态 =====");
// 主线程等待一段时间,让其他线程有机会完成(实际开发中应使用更可靠的同步机制)
Thread.sleep(200);
// 打印各线程的状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)
System.out.println("thread1状态:" + thread1.getState());
System.out.println("thread2状态:" + thread2.getState());
System.out.println("thread3状态:" + thread3.getState());
System.out.println("\n主线程执行完毕");
}
}
43.2 线程同步:共享资源的并发访问控制
线程同步就像 “多车通行的红绿灯”—— 当多个线程访问共享资源(如账户余额、文件句柄)时,需要同步机制避免 “冲突”(如脏读、数据不一致)。
package com.thread.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程同步机制简化演示
* 展示不同同步方式保护共享资源的用法
* 包含synchronized方法、synchronized代码块、ReentrantLock和volatile关键字的使用示例
*/
public class ThreadSync {
// 初始账户余额,多线程共享的核心资源
private double accountBalance = 1000.0;
// 用于synchronized代码块的锁对象
private final Object syncObject = new Object();
// 显式可重入锁,用于更灵活的同步控制
private final Lock reentrantLock = new ReentrantLock();
// 记录交易次数,多线程共享的计数器
private int transactionCount = 0;
/**
* synchronized方法示例:存款操作
* 使用this对象作为锁,确保同一时间只有一个线程能执行此方法
*
* @param amount 存款金额
*/
public synchronized void deposit(double amount) {
// 原子性操作:更新余额和交易计数
accountBalance += amount;
transactionCount++;
}
/**
* synchronized代码块示例:取款操作
* 使用专用锁对象,粒度更细的同步控制
*
* @param amount 取款金额
*/
public void withdraw(double amount) {
// 获取锁,确保临界区代码的原子性
synchronized (syncObject) {
if (accountBalance >= amount) {
accountBalance -= amount;
transactionCount++;
}
}
}
/**
* ReentrantLock示例:账户间转账
* 使用显式锁实现跨对象的原子操作
*
* @param targetAccount 目标账户
* @param amount 转账金额
*/
public void transfer(ThreadSync targetAccount, double amount) {
// 获取当前账户和目标账户的锁,按固定顺序避免死锁
this.reentrantLock.lock();
targetAccount.reentrantLock.lock();
try {
if (this.accountBalance >= amount) {
// 原子性转账操作
this.withdraw(amount);
targetAccount.deposit(amount);
System.out.println("成功转账: " + amount);
} else {
System.out.println("余额不足,转账失败");
}
} finally {
// 必须在finally块中释放锁,确保异常情况下锁也能释放
targetAccount.reentrantLock.unlock();
this.reentrantLock.unlock();
}
}
/**
* 显示账户信息
*/
public void showAccountInfo() {
System.out.println("当前余额: " + accountBalance);
System.out.println("交易次数: " + transactionCount);
}
/**
* 主方法:演示多线程操作
*
* @param args 命令行参数
* @throws InterruptedException 线程中断异常
*/
public static void main(String[] args) throws InterruptedException {
// 创建两个账户用于演示多线程转账
ThreadSync accountA = new ThreadSync();
ThreadSync accountB = new ThreadSync();
// 显示初始状态
System.out.println("账户A初始状态:");
accountA.showAccountInfo();
System.out.println("\n账户B初始状态:");
accountB.showAccountInfo();
// 创建并启动转账线程,模拟并发操作
Thread transferThread1 = new Thread(() -> {
for (int i = 0; i < 500; i++) {
accountA.transfer(accountB, 10.0);
try {
Thread.sleep(1); // 模拟业务处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread transferThread2 = new Thread(() -> {
for (int i = 0; i < 500; i++) {
accountB.transfer(accountA, 5.0);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动并发转账操作
transferThread1.start();
transferThread2.start();
// 等待所有转账操作完成
transferThread1.join();
transferThread2.join();
// 显示最终状态,验证线程安全
System.out.println("\n账户A最终状态:");
accountA.showAccountInfo();
System.out.println("\n账户B最终状态:");
accountB.showAccountInfo();
// 演示volatile关键字的可见性保证
VolatileFlag flagDemo = new VolatileFlag();
Thread countingThread = new Thread(flagDemo::countNumbers);
countingThread.start();
// 主线程休眠2秒后停止计数线程
Thread.sleep(2000);
flagDemo.stopCounting();
countingThread.join();
System.out.println("\n计数结果: " + flagDemo.getCount());
}
/**
* volatile关键字演示类
* 展示volatile如何保证变量的可见性
*/
static class VolatileFlag {
// 使用volatile修饰的标志位,确保所有线程立即看到其修改
private volatile boolean keepCounting = true;
// 计数器,记录循环次数
private int count = 0;
/**
* 计数方法,在keepCounting为true时持续计数
*/
public void countNumbers() {
System.out.println("开始计数...");
while (keepCounting) {
count++;
// 短暂休眠避免CPU占用过高
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("计数停止");
}
/**
* 停止计数方法,修改volatile标志位
*/
public void stopCounting() {
this.keepCounting = false;
System.out.println("已设置停止标志");
}
/**
* 获取当前计数结果
*
* @return 计数值
*/
public int getCount() {
return count;
}
}
}
43.3 线程协作:多线程任务的协同机制
ava 线程协作的核心工具——CountDownLatch 和 Semaphore,它们就像 “团队任务的指挥官” 与 “资源访问的门卫”,能精准控制多线程的执行节奏!
package com.thread.demo;
import java.util.concurrent.*;
/**
* 线程协作
* 展示Java中常用的线程同步工具和异步编程模式
*/
public class ThreadCooperation {
// 1. CountDownLatch演示:等待所有任务完成后继续执行
public void countDownLatchDemo() throws InterruptedException {
System.out.println("===== CountDownLatch演示 =====");
// 创建CountDownLatch,设置需要等待的任务数量
int totalTasks = 5;
// CountDownLatch通过计数器控制主线程等待,每完成一个任务计数器减1
CountDownLatch latch = new CountDownLatch(totalTasks);
// 创建并启动多个任务线程
for (int i = 0; i < totalTasks; i++) {
final int taskId = i;
new Thread(() -> {
try {
System.out.println("任务" + taskId + "开始执行");
Thread.sleep(1000); // 模拟任务执行耗时
System.out.println("任务" + taskId + "执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 任务完成后,调用countDown()减少计数
// 每个任务完成时必须调用countDown,否则主线程将永远等待
latch.countDown();
System.out.println("任务" + taskId + "已通知CountDownLatch");
}
}).start();
}
// 主线程在此阻塞,直到所有任务完成(计数为0)
System.out.println("主线程等待所有任务完成...");
// await()方法会阻塞当前线程,直到计数器变为0
latch.await();
System.out.println("所有任务已完成,主线程继续执行");
}
// 2. Semaphore演示:控制同时访问资源的线程数量
public void semaphoreDemo() {
System.out.println("\n===== Semaphore演示 =====");
// 创建Semaphore,设置允许同时访问的最大线程数
int maxConcurrentAccess = 2;
// Semaphore维护一组许可(permits),用于控制并发访问资源的数量
Semaphore semaphore = new Semaphore(maxConcurrentAccess);
// 创建线程池执行多个任务
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交5个任务,但最多只允许2个线程同时执行
for (int i = 0; i < 5; i++) {
final int threadId = i;
executor.submit(() -> {
try {
// 获取许可,如果没有可用许可则阻塞
// acquire()方法会获取一个许可,如果没有许可则阻塞
semaphore.acquire();
System.out.println("线程" + threadId + "获取到许可,开始访问资源");
// 模拟资源访问
Thread.sleep(1000);
System.out.println("线程" + threadId + "完成访问,释放许可");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可,允许其他线程获取
// 必须在finally块中释放许可,确保资源一定会被释放
semaphore.release();
}
});
}
// 关闭线程池
executor.shutdown();
}
public static void main(String[] args) throws InterruptedException {
ThreadCooperation demo = new ThreadCooperation();
demo.countDownLatchDemo();
Thread.sleep(1000); // 确保CountDownLatch演示完成后再进行下一个演示
demo.semaphoreDemo();
Thread.sleep(3000); // 等待Semaphore演示中的线程池任务完成
}
}
总结多线程编程的适用场景
1. 高并发服务:Web 服务器处理多个客户端请求(如 Tomcat 的线程池模型)。
2. 实时数据处理:日志收集、监控数据采集等需要持续运行的任务。
3. IO 密集型操作:文件读写、网络通信等场景,利用多线程提高资源利用率。
4. 分布式计算:大数据处理中的任务拆分与并行计算(如 MapReduce 的并行阶段)。
下期将学习IO和NIO编程,记得点赞关注,评论区留下你对多线程编程的疑问,我们一起解决!