Java多线程编程从入门到实战(保姆级教程)

一、为什么每个Java程序员都要懂多线程?

(敲黑板!)大家有没有遇到过这样的场景:你的程序运行速度像蜗牛一样慢,CPU利用率却低得可怜?这时候就该多线程登场了!举个真实案例——去年我帮朋友优化一个电商秒杀系统,单线程处理请求时QPS(每秒查询率)只能到50,改成多线程后直接飙升到2000+!

不过先别急着兴奋,多线程就像一把双刃剑。搞得好性能飞升,搞不好就是各种bug满天飞(别问我怎么知道的,都是泪啊)。咱们先来搞懂几个核心概念:

  • 进程 vs 线程:进程是独立的应用,线程是进程里的执行单元(可以理解为工厂和工人的关系)
  • 并行 vs 并发:并行是真·同时执行(需要多核CPU),并发是快速切换的假象(单核也能玩)

二、手把手教你创建线程的3种姿势

1. 继承Thread类(适合简单场景)

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程启动:" + Thread.currentThread().getName());
    }
}

// 使用示例
new MyThread().start();

2. 实现Runnable接口(推荐方式)

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("实现Runnable的线程:" + Thread.currentThread().getName());
    }
}

// 启动线程的正确姿势
new Thread(new MyRunnable()).start();

3. 使用Callable+Future(需要返回值时用)

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
    TimeUnit.SECONDS.sleep(2);
    return 42;
});

// 获取返回值(会阻塞!)
Integer result = future.get(); 

三、线程的生命周期全解析

(重点预警!)线程的一生要经历这些状态:

  1. NEW → 刚出生的小宝宝
  2. RUNNABLE → 准备就绪的运动员
  3. BLOCKED → 被关小黑屋了
  4. WAITING → 望眼欲穿的等待
  5. TIMED_WAITING → 带计时器的等待
  6. TERMINATED → 光荣退休

看个状态转换图更直观:

启动线程       获取锁失败        wait()被调用     sleep(timeout)
NEW → RUNNABLE → BLOCKED ←---------------------→ WAITING
       |  ^          |           notify()/notifyAll()
       |  |          | join(timeout)
       V  |          V
   RUNNING           TIMED_WAITING

四、线程同步的5大杀器

1. synchronized关键字(基础款)

// 同步方法
public synchronized void transfer(int amount) {
    // 转账操作
}

// 同步代码块(更灵活)
public void update() {
    synchronized(this) {
        // 临界区代码
    }
}

2. ReentrantLock(高配版)

Lock lock = new ReentrantLock();

void safeMethod() {
    lock.lock();
    try {
        // 危险操作
    } finally {
        lock.unlock(); // 必须放在finally块!
    }
}

3. 原子类(无锁编程神器)

AtomicInteger counter = new AtomicInteger(0);

// 线程安全的自增
counter.incrementAndGet();

4. CountDownLatch(多人赛跑发令枪)

CountDownLatch latch = new CountDownLatch(3);

// 工作线程
new Thread(() -> {
    doWork();
    latch.countDown();
}).start();

// 主线程等待
latch.await(); // 阻塞直到计数器归零

5. CyclicBarrier(循环路障)

CyclicBarrier barrier = new CyclicBarrier(3, () -> 
    System.out.println("所有玩家已就位!"));

// 每个线程到达屏障点时
barrier.await(); // 会阻塞直到所有线程到达

五、线程池的正确打开方式

(血泪教训!)千万别再new Thread了!线程池才是王道:

// 创建线程池的正确姿势
ExecutorService pool = Executors.newFixedThreadPool(5);

// 提交任务
for (int i = 0; i < 100; i++) {
    pool.execute(() -> {
        // 执行具体任务
    });
}

// 优雅关闭
pool.shutdown();
pool.awaitTermination(1, TimeUnit.HOURS);

线程池参数调优的黄金法则:

  • corePoolSize:日常并发量
  • maximumPoolSize:最大承载量(建议不要超过CPU核心数*2)
  • keepAliveTime:闲置线程存活时间
  • workQueue:任务队列(ArrayBlockingQueue vs LinkedBlockingQueue)

六、实战:模拟12306抢票系统

来,咱们用多线程实现一个真实的抢票场景:

class TicketSystem {
    private AtomicInteger tickets = new AtomicInteger(100);
    
    public boolean grabTicket(String user) {
        int remaining = tickets.decrementAndGet();
        if(remaining >= 0) {
            System.out.println(user + "抢票成功!剩余票数:" + remaining);
            return true;
        }
        System.out.println(user + "手慢了...");
        return false;
    }
}

// 模拟1000人同时抢票
TicketSystem system = new TicketSystem();
ExecutorService pool = Executors.newFixedThreadPool(20);

for (int i = 0; i < 1000; i++) {
    final String user = "用户" + i;
    pool.execute(() -> system.grabTicket(user));
}

运行这个程序你会看到:

  1. 票数可能变成负数(如果不做同步控制)
  2. 同一张票可能被多个人抢到(经典的线程安全问题)
  3. 打印顺序混乱(这就是线程执行的随机性)

要解决这些问题,就需要用到前面讲的同步机制。试试用synchronized或ReentrantLock改造这个程序吧!

七、避坑指南(血泪总结)

  1. 死锁预防四原则

    • 按固定顺序获取锁
    • 设置锁超时时间
    • 使用tryLock()
    • 避免嵌套锁
  2. 性能优化三板斧

    • 减少锁的粒度(比如用ConcurrentHashMap代替synchronizedMap)
    • 使用读写锁(ReentrantReadWriteLock)
    • 尽量使用无锁数据结构(Atomic类、LongAdder)
  3. 常见异常处理

    • InterruptedException:正确处理线程中断
    • RejectedExecutionException:合理配置线程池拒绝策略
    • TimeoutException:设置合理的超时时间
  4. 内存可见性陷阱

    • 总是用volatile修饰会被多个线程访问的变量
    • 或者使用Atomic类保证原子性
    • 不要依赖普通的++操作(它根本不是原子操作!)

八、学习路线图(打怪升级指南)

  1. 新手村:掌握Thread/Runnable基础用法
  2. 初级副本:理解synchronized和volatile
  3. 中级挑战:玩转JUC包(java.util.concurrent)
  4. 高级战场:研究AQS(AbstractQueuedSynchronizer)源码
  5. 终极BOSS:掌握Disruptor等高性能并发框架

最后送大家一句话:多线程编程就像在钢丝上跳舞,既要保持平衡(线程安全),又要追求优雅(高性能)。多写多练,多踩坑,才是最快的成长方式!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值