42. 多线程编程解析,并发并行艺术

42. 多线程编程解析,并发并行艺术

编码文章call10242025-07-16 13:45:425A+A-

哈喽 大家好!

我是老陈,这节课一起来学习多线程编程 , 多线程就像 “程序的并行流水线”,让多个任务,如数据计算、网络请求、界面响应同时执行,充分利用多核 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编程,记得点赞关注,评论区留下你对多线程编程的疑问,我们一起解决!



#java##程序员##计算机##热门##热搜##今日头条##人工智能#

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4