Java多线程案例入门

wen java案例 4

Java多线程案例入门:从零到实战的完整指南

目录导读

  1. 为什么必须掌握多线程?
  2. 线程基础概念速览(含核心问答)
  3. 三种经典创建线程的方式及代码案例
  4. 线程安全与锁机制:synchronized + Lock实战
  5. 生产者-消费者模式:wait/notify与BlockingQueue双解法
  6. 高频面试题问答(线程池、死锁、volatile)
  7. 总结与最佳实践建议

为什么必须掌握多线程?

在CPU多核普及的今天,单线程程序无法充分利用硬件资源,Java多线程能显著提升程序响应速度和吞吐量,据Stack Overflow 2023年调查,多线程与并发是Java开发者最关注的核心技能之一,本篇文章通过实战案例,帮助初学者快速建立多线程编程思维。

Java多线程案例入门

线程基础概念速览

  • 进程:资源分配的单位;线程:CPU调度的最小单位。
  • 线程状态:NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(超时等待)、TERMINATED(终止)。
  • 上下文切换:线程调度开销,避免过多线程。

核心问答:线程和进程的区别?

:进程拥有独立的地址空间和资源,而线程共享进程内存,切换成本更低,多线程适合I/O密集型任务,多进程适合CPU密集且需要强隔离的场景。

三种经典创建线程的方式及代码案例

案例1:继承Thread

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 运行中");
    }
}
// 调用:new MyThread().start();

案例2:实现Runnable接口(推荐)

Runnable task = () -> System.out.println("Runnable执行");
Thread t = new Thread(task);
t.start();

案例3:实现Callable接口(带返回值)

Callable<Integer> callable = () -> { return 1 + 2; };
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
System.out.println(futureTask.get()); // 输出3

实战提醒:通常使用RunnableCallable,避免单继承限制。

线程安全与锁机制详解

问题场景:多个线程同时对计数器累加

public class Counter {
    private int count = 0;
    public void increment() { count++; } // 非原子性操作
}

现象:最终结果小于预期值,因为count++是“读-改-写”三步,非线程安全。

synchronized关键字

public synchronized void increment() { count++; }
  • 优缺点:自动加锁/释放,但无法中断等待、性能较低。

Lock接口 + ReentrantLock

Lock lock = new ReentrantLock();
public void increment() {
    lock.lock();
    try { count++; } finally { lock.unlock(); }
}
  • 优点:支持公平锁、超时、可中断。

核心问答:synchronizedLock哪个更好?

synchronized代码简洁,JVM自动管理锁;Lock更灵活(如尝试非阻塞获取锁tryLock()),适合复杂并发场景,目前JDK 15+对synchronized做了大量优化(偏向锁、轻量级锁),性能差距缩小。

生产者-消费者模式:wait/notify与BlockingQueue

经典案例(使用wait/notify)

class Producer implements Runnable {
    private Queue<Integer> queue;
    private int maxSize;
    @Override
    public void run() {
        while(true) {
            synchronized(queue) {
                while(queue.size() == maxSize) {
                    try { queue.wait(); } catch (InterruptedException e) { }
                }
                queue.add(new Random().nextInt());
                queue.notifyAll();
            }
        }
    }
}
// 消费者类似,判空条件后wait

更简洁的现代写法:ArrayBlockingQueue

BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产者:queue.put(1); 消费者:queue.take();
  • put()take()自动阻塞等待,避免手动通知,极大降低bug风险。

核心问答:为什么用while而非if判断条件?

:防止虚假唤醒(线程在wait()返回后条件不再满足),使用while循环重新检查,保证线程安全。

高频面试题问答(进阶)

Q1:线程池有哪些参数?怎么配置合理?

:核心参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、workQueue(任务队列)、keepAliveTime,建议:

  • CPU密集:核心线程 = CPU核数
  • I/O密集:核心线程 = CPU核数 * 2
  • 使用Executors.newFixedThreadPool(n)时注意队列无界风险,生产环境建议ThreadPoolExecutor手动配置。

Q2:volatile能保证线程安全吗?

volatile保证可见性有序性(禁止指令重排),但不保证原子性,例如volatile int count; count++依然不是线程安全的,需配合synchronizedAtomicInteger

Q3:如何避免死锁?

:死锁四大必要条件:互斥、占有并等待、不可剥夺、循环等待,打破任意一条即可,常见手段:加锁顺序一致、使用tryLock超时返回、减少锁粒度。

总结与最佳实践建议

  • 优先使用高级并发工具BlockingQueueThreadPoolExecutorCompletableFuture)而非底层API。
  • 线程安全目标:共享数据不可变 > 线程封闭 > 同步控制。
  • 性能调优:线程粒度不要过细,避免大量上下文切换。
  • 测试:多线程问题难以复现,使用jstackjvisualvm排查死锁和内存竞争。

一句话总结:多线程编程的核心不是“开更多线程”,而是安全地管理共享资源的并发访问,掌握本文案例,即可应对70%以上的日常并发需求。


延伸阅读:如果你想深入并发设计模式(如Fork/Join、并行流),可以关注后续的《Java并发进阶:从锁到无锁编程》,希望本文对你的学习之路有实际帮助!

抱歉,评论功能暂时关闭!