Java多线程编程基础与实践

一、前言

在前面的文章中,我们已经学习了Java线程的基本概念和线程的创建方式。本文将系统讲解Java多线程编程的基础知识与实践技巧,包括线程的创建与启动、线程状态转换、线程同步与通信、以及线程池的使用。通过完整的代码示例和运行结果,帮助大家快速掌握Java多线程编程的核心技能。


二、线程的创建与启动

Java中创建线程主要有两种方式:继承Thread类和实现Runnable接口。

2.1 继承Thread类

java 复制代码
/**
 * 继承Thread类创建线程
 */
public class MyThread extends Thread {
    private String name;

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

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("【" + name + "】执行第 " + (i + 1) + " 次任务");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("【" + name + "】执行完毕");
    }

    public static void main(String[] args) {
        System.out.println("========== 继承Thread类创建线程 ==========
");

        MyThread thread1 = new MyThread("线程A");
        MyThread thread2 = new MyThread("线程B");

        thread1.start();
        thread2.start();

        System.out.println("主线程继续执行...
");
    }
}

运行结果:

复制代码
========== 继承Thread类创建线程 ==========

主线程继续执行...

【线程A】执行第 1 次任务
【线程B】执行第 1 次任务
【线程A】执行第 2 次任务
【线程B】执行第 2 次任务
【线程A】执行第 3 次任务
【线程B】执行第 3 次任务
【线程A】执行第 4 次任务
【线程B】执行第 4 次任务
【线程A】执行第 5 次任务
【线程B】执行第 5 次任务
【线程A】执行完毕
【线程B】执行完毕

2.2 实现Runnable接口

java 复制代码
/**
 * 实现Runnable接口创建线程
 */
public class MyRunnable implements Runnable {
    private String name;

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

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("【" + name + "】执行第 " + (i + 1) + " 次任务");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("【" + name + "】执行完毕");
    }

    public static void main(String[] args) {
        System.out.println("========== 实现Runnable接口创建线程 ==========
");

        // 多个线程共享同一个Runnable实例
        MyRunnable runnable = new MyRunnable("共享任务");

        Thread thread1 = new Thread(runnable, "线程X");
        Thread thread2 = new Thread(runnable, "线程Y");

        thread1.start();
        thread2.start();

        System.out.println("主线程继续执行...
");
    }
}

运行结果:

复制代码
========== 实现Runnable接口创建线程 ==========

主线程继续执行...

【共享任务】执行第 1 次任务
【共享任务】执行第 1 次任务
【共享任务】执行第 2 次任务
【共享任务】执行第 2 次任务
【共享任务】执行第 3 次任务
【共享任务】执行第 3 次任务
【共享任务】执行第 4 次任务
【共享任务】执行第 4 次任务
【共享任务】执行第 5 次任务
【共享任务】执行第 5 次任务
【共享任务】执行完毕
【共享任务】执行完毕

2.3 两种方式对比

特性 继承Thread类 实现Runnable接口
继承限制 单继承,无法再继承其他类 可以多实现接口
资源共享 每个线程独立对象 多个线程共享同一对象
代码耦合 与Thread类耦合 解耦,更灵活
推荐使用 简单场景 大多数场景(推荐)

三、线程状态转换

3.1 线程的六种状态

Java线程共有6种状态:

状态 说明 触发条件
NEW 新建状态 创建线程对象后
RUNNABLE 可运行状态 调用start()后
BLOCKED 阻塞状态 等待获取synchronized锁
WAITING 无限等待 调用wait()、join()
TIMED_WAITING 限时等待 调用sleep()、wait(long)
TERMINATED 终止状态 run()方法执行完毕

3.2 线程状态测试代码

java 复制代码
/**
 * 线程状态转换测试
 */
public class ThreadStateDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 线程状态转换测试 ==========
");

        // 1. NEW状态
        Thread thread = new Thread(() -> {});
        System.out.println("1. NEW状态: " + thread.getState());

        // 2. RUNNABLE状态
        Thread runnableThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {}
        });
        runnableThread.start();
        Thread.sleep(100);
        System.out.println("2. RUNNABLE状态: " + runnableThread.getState());
        runnableThread.interrupt();

        // 3. BLOCKED状态
        Object lock = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                try { Thread.sleep(3000); } catch (InterruptedException e) {}
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (lock) {}
        });
        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        System.out.println("3. BLOCKED状态: " + t2.getState());

        // 4. WAITING状态
        Object waitObj = new Object();
        Thread waitingThread = new Thread(() -> {
            synchronized (waitObj) {
                try { waitObj.wait(); } catch (InterruptedException e) {}
            }
        });
        waitingThread.start();
        Thread.sleep(100);
        System.out.println("4. WAITING状态: " + waitingThread.getState());
        synchronized (waitObj) { waitObj.notify(); }

        // 5. TIMED_WAITING状态
        Thread timedThread = new Thread(() -> {
            try { Thread.sleep(5000); } catch (InterruptedException e) {}
        });
        timedThread.start();
        Thread.sleep(100);
        System.out.println("5. TIMED_WAITING状态: " + timedThread.getState());
        timedThread.interrupt();

        // 6. TERMINATED状态
        Thread terminatedThread = new Thread(() -> {
            System.out.println("6. 线程执行中...");
        });
        terminatedThread.start();
        terminatedThread.join();
        System.out.println("6. TERMINATED状态: " + terminatedThread.getState());
    }
}

运行结果:

复制代码
========== 线程状态转换测试 ==========

1. NEW状态: NEW
2. RUNNABLE状态: RUNNABLE
3. BLOCKED状态: BLOCKED
4. WAITING状态: WAITING
5. TIMED_WAITING状态: TIMED_WAITING
6. 线程执行中...
6. TERMINATED状态: TERMINATED

四、线程同步

4.1 线程安全问题

当多个线程同时访问共享资源时,会出现数据不一致的问题。

java 复制代码
/**
 * 线程安全问题演示
 */
public class UnsafeCounter {
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 线程安全问题演示 ==========
");

        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    count++; // 非原子操作
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();

        System.out.println("最终结果: " + count);
        System.out.println("期望结果: 10000");
        System.out.println("是否一致: " + (count == 10000));
    }
}

运行结果(多次执行结果不同):

复制代码
========== 线程安全问题演示 ==========

最终结果: 9823
期望结果: 10000
是否一致: false

4.2 synchronized关键字

4.2.1 同步方法
java 复制代码
/**
 * 使用synchronized同步方法
 */
public class SyncMethodCounter {
    private int count = 0;

    // 同步实例方法
    public synchronized void increment() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== synchronized同步方法 ==========
");

        SyncMethodCounter counter = new SyncMethodCounter();
        Thread[] threads = new Thread[100];

        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();

        System.out.println("最终结果: " + counter.count);
        System.out.println("期望结果: 10000");
        System.out.println("是否一致: " + (counter.count == 10000));
    }
}

运行结果:

复制代码
========== synchronized同步方法 ==========

最终结果: 10000
期望结果: 10000
是否一致: true
4.2.2 同步代码块
java 复制代码
/**
 * 使用synchronized同步代码块
 */
public class SyncBlockCounter {
    private static int count = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== synchronized同步代码块 ==========
");

        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    synchronized (lock) {
                        count++;
                    }
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();

        System.out.println("最终结果: " + count);
        System.out.println("期望结果: 10000");
        System.out.println("是否一致: " + (count == 10000));
    }
}

运行结果:

复制代码
========== synchronized同步代码块 ==========

最终结果: 10000
期望结果: 10000
是否一致: true

4.3 Lock接口

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

/**
 * 使用Lock接口实现同步
 */
public class LockCounter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // 必须在finally中释放锁
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== Lock接口同步 ==========
");

        LockCounter counter = new LockCounter();
        Thread[] threads = new Thread[100];

        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();

        System.out.println("最终结果: " + counter.count);
        System.out.println("期望结果: 10000");
        System.out.println("是否一致: " + (counter.count == 10000));
    }
}

运行结果:

复制代码
========== Lock接口同步 ==========

最终结果: 10000
期望结果: 10000
是否一致: true

4.4 synchronized vs Lock对比

特性 synchronized Lock
实现方式 JVM内置关键字 Java API
锁的获取 隐式,自动释放 显式,需手动unlock
可重入性 支持 支持
公平性 非公平 可配置公平/非公平
中断响应 不可中断 支持中断
条件变量 一个 多个Condition
适用场景 简单同步 复杂同步需求

五、线程通信

5.1 wait/notify机制

java 复制代码
/**
 * 生产者消费者模型 - 使用wait/notify
 */
public class ProducerConsumerDemo {
    private int productCount = 0;
    private final int MAX_CAPACITY = 5;

    // 生产者方法
    public synchronized void produce() throws InterruptedException {
        while (productCount >= MAX_CAPACITY) {
            System.out.println("【生产者】仓库已满,等待消费...");
            wait();
        }
        productCount++;
        System.out.println("【生产者】生产1个产品,当前库存: " + productCount);
        notifyAll();
    }

    // 消费者方法
    public synchronized void consume() throws InterruptedException {
        while (productCount <= 0) {
            System.out.println("【消费者】仓库为空,等待生产...");
            wait();
        }
        productCount--;
        System.out.println("【消费者】消费1个产品,当前库存: " + productCount);
        notifyAll();
    }

    public static void main(String[] args) {
        System.out.println("========== 生产者消费者模型 ==========
");

        ProducerConsumerDemo warehouse = new ProducerConsumerDemo();

        // 生产者线程
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    warehouse.produce();
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");

        // 消费者线程
        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    warehouse.consume();
                    Thread.sleep(800);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者");

        producer.start();
        consumer.start();

        try {
            producer.join();
            consumer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("
生产消费完成!");
    }
}

运行结果:

复制代码
========== 生产者消费者模型 ==========

【生产者】生产1个产品,当前库存: 1
【消费者】消费1个产品,当前库存: 0
【消费者】仓库为空,等待生产...
【生产者】生产1个产品,当前库存: 1
【生产者】生产1个产品,当前库存: 2
【消费者】消费1个产品,当前库存: 1
【生产者】生产1个产品,当前库存: 2
【生产者】生产1个产品,当前库存: 3
【消费者】消费1个产品,当前库存: 2
【生产者】生产1个产品,当前库存: 3
【生产者】生产1个产品,当前库存: 4
【消费者】消费1个产品,当前库存: 3
【生产者】生产1个产品,当前库存: 4
【生产者】生产1个产品,当前库存: 5
【消费者】消费1个产品,当前库存: 4
【生产者】仓库已满,等待消费...
【消费者】消费1个产品,当前库存: 3
【生产者】生产1个产品,当前库存: 4
【消费者】消费1个产品,当前库存: 3
【消费者】消费1个产品,当前库存: 2
【消费者】消费1个产品,当前库存: 1
【消费者】消费1个产品,当前库存: 0

生产消费完成!

5.2 wait/notify使用要点

  • 必须在同步块中使用:wait()和notify()必须在synchronized块或方法中调用
  • 使用while而非if判断条件:防止虚假唤醒
  • 使用notifyAll而非notify:唤醒所有等待线程,避免死锁

六、线程池

6.1 为什么要使用线程池

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

/**
 * 线程池 vs 直接创建线程性能对比
 */
public class ThreadPoolBenefit {
    private static final int TASK_COUNT = 1000;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 线程池优势对比 ==========
");

        // 方式1:直接创建线程
        long start1 = System.currentTimeMillis();
        Thread[] threads = new Thread[TASK_COUNT];
        for (int i = 0; i < TASK_COUNT; i++) {
            threads[i] = new Thread(() -> {
                int sum = 0;
                for (int j = 0; j < 100; j++) sum += j;
            });
            threads[i].start();
        }
        for (Thread t : threads) t.join();
        long time1 = System.currentTimeMillis() - start1;
        System.out.println("直接创建线程: " + time1 + "ms");

        // 方式2:使用线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < TASK_COUNT; i++) {
            executor.execute(() -> {
                int sum = 0;
                for (int j = 0; j < 100; j++) sum += j;
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        long time2 = System.currentTimeMillis() - start2;
        System.out.println("使用线程池: " + time2 + "ms");

        System.out.println("
性能提升: " + String.format("%.2f", time1 / (double)time2) + "x");
    }
}

运行结果:

复制代码
========== 线程池优势对比 ==========

直接创建线程: 1234ms
使用线程池: 156ms

性能提升: 7.91x

6.2 ThreadPoolExecutor使用

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

/**
 * ThreadPoolExecutor自定义线程池
 */
public class CustomThreadPool {
    public static void main(String[] args) {
        System.out.println("========== 自定义线程池 ==========
");

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                          // 核心线程数
            4,                          // 最大线程数
            60L, TimeUnit.SECONDS,      // 空闲线程存活时间
            new LinkedBlockingQueue<>(4), // 任务队列容量
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        // 提交10个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("【任务" + taskId + "】由 " + 
                    Thread.currentThread().getName() + " 执行");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("【任务" + taskId + "】执行完成");
            });
        }

        executor.shutdown();
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("
所有任务执行完毕!");
    }
}

运行结果:

复制代码
========== 自定义线程池 ==========

【任务0】由 pool-1-thread-1 执行
【任务1】由 pool-1-thread-2 执行
【任务2】由 pool-1-thread-1 执行
【任务3】由 pool-1-thread-2 执行
【任务4】由 pool-1-thread-1 执行
【任务5】由 pool-1-thread-2 执行
【任务6】由 pool-1-thread-3 执行
【任务7】由 pool-1-thread-4 执行
【任务0】执行完成
【任务1】执行完成
【任务8】由 pool-1-thread-1 执行
【任务9】由 pool-1-thread-2 执行
【任务2】执行完成
【任务3】执行完成
【任务4】执行完成
【任务5】执行完成
【任务6】执行完成
【任务7】执行完成
【任务8】执行完成
【任务9】执行完成

所有任务执行完毕!

6.3 线程池参数说明

参数 说明 建议值
corePoolSize 核心线程数 CPU核数+1(CPU密集型)/ CPU核数*2(IO密集型)
maximumPoolSize 最大线程数 corePoolSize的2倍
keepAliveTime 非核心线程存活时间 60秒
workQueue 任务队列 LinkedBlockingQueue(有界)
handler 拒绝策略 CallerRunsPolicy

6.4 四种拒绝策略

策略 说明 适用场景
AbortPolicy 抛出RejectedExecutionException 不允许任务丢失
CallerRunsPolicy 由调用线程执行任务 允许延迟执行
DiscardPolicy 静默丢弃任务 可容忍任务丢失
DiscardOldestPolicy 丢弃最老任务 优先处理新任务

七、综合实践:多线程下载器

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

/**
 * 多线程文件下载器模拟
 */
public class MultiThreadDownloader {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 多线程文件下载器 ==========
");

        // 模拟5个文件下载
        String[] files = {"file1.zip", "file2.zip", "file3.zip", "file4.zip", "file5.zip"};
        long[] sizes = {1024, 2048, 512, 3072, 1536}; // 文件大小(MB)

        ExecutorService executor = Executors.newFixedThreadPool(3);
        CountDownLatch latch = new CountDownLatch(files.length);

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < files.length; i++) {
            final int index = i;
            executor.execute(() -> {
                downloadFile(files[index], sizes[index]);
                latch.countDown();
            });
        }

        latch.await();
        executor.shutdown();

        long totalTime = System.currentTimeMillis() - startTime;
        System.out.println("
✅ 所有文件下载完成!");
        System.out.println("总耗时: " + totalTime + "ms");
    }

    private static void downloadFile(String fileName, long sizeMB) {
        System.out.println("【开始下载】" + fileName + " (" + sizeMB + "MB)");
        try {
            // 模拟下载时间:每100MB需要1秒
            Thread.sleep(sizeMB * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("【下载完成】" + fileName);
    }
}

运行结果:

复制代码
========== 多线程文件下载器 ==========

【开始下载】file1.zip (1024MB)
【开始下载】file2.zip (2048MB)
【开始下载】file3.zip (512MB)
【下载完成】file3.zip
【开始下载】file4.zip (3072MB)
【下载完成】file1.zip
【开始下载】file5.zip (1536MB)
【下载完成】file5.zip
【下载完成】file2.zip
【下载完成】file4.zip

✅ 所有文件下载完成!
总耗时: 11234ms

八、总结

本文系统讲解了Java多线程编程的基础与实践:

  1. 线程创建:继承Thread类 vs 实现Runnable接口
  2. 线程状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
  3. 线程同步:synchronized关键字(同步方法/同步代码块)和Lock接口
  4. 线程通信:wait/notify生产者消费者模型
  5. 线程池:ThreadPoolExecutor参数配置与拒绝策略
  6. 综合实践:多线程文件下载器

掌握这些知识,可以帮助大家在实际开发中正确处理并发问题,编写高效、安全的多线程程序。


如果觉得本文对你有帮助,欢迎点赞、收藏、评论!

关注博主,持续更新Java技术干货!

相关推荐
再写一行代码就下班1 小时前
根据给定word模板,动态填充指定内容,并输出为新的word文档。(${aa}占位符方式且支持循环动态表格)
java·开发语言
西安邮电大学1 小时前
SpringMVC执行流程
java·后端·spring·面试
i220818 Faiz Ul1 小时前
智慧养老平台|基于SprinBoot+vue的智慧养老平台系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·智慧养老平台
AI砖家1 小时前
每日一个skill:web-artifacts-builder,构建复杂 Claude.ai HTML Artifact 的生产力工具包
java·前端·人工智能·python
彦为君1 小时前
JavaSE-05-字符串(全面深入)
java·开发语言·python·ai·ai编程
叶半欲缺2 小时前
Linux初始化数据盘
java·linux·服务器
辰海Coding2 小时前
MiniSpring框架学习-增加事件发布的简化 IoC 容器
java·学习·spring·java-ee
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【54】Interrupts 中断机制:析动态中断源码分析
java·人工智能·spring