首页 开发教程 开始改变第四天 Java并发(2)

开始改变第四天 Java并发(2)

开发教程 2025年12月4日
612 浏览

并发编程:像管理团队一样管理线程

一、并发 vs 并行 vs 串行:厨房工作模式

1.1 串行:一个人做完所有事

生活比喻:家里只有你一个人做饭,必须按顺序:洗菜切菜 → 炒菜 → 装盘

public class SerialCooking {
    public static void main(String[] args) {
        System.out.println(\"‍ 开始做饭...\");
        
        washVegetables();  // 洗菜 - 3秒
        cutVegetables();   // 切菜 - 2秒  
        cook();           // 炒菜 - 4秒
        serve();          // 装盘 - 1秒
        
        System.out.println(\" 所有任务完成!\");
    }
    
    static void washVegetables() {
        try {
            Thread.sleep(3000);
            System.out.println(\" 菜洗好了\");
        } catch (InterruptedException e) {}
    }
    
    static void cutVegetables() {
        try {
            Thread.sleep(2000);
            System.out.println(\" 菜切好了\");
        } catch (InterruptedException e) {}
    }
    
    static void cook() {
        try {
            Thread.sleep(4000);
            System.out.println(\" 菜炒好了\");
        } catch (InterruptedException e) {}
    }
    
    static void serve() {
        try {
            Thread.sleep(1000);
            System.out.println(\"️  菜装盘了\");
        } catch (InterruptedException e) {}
    }
}

结果:总共10秒,一个人干完所有活

1.2 并发:一个人轮流做多件事

生活比喻:你还是一个人,但边煮饭边准备其他菜,快速切换

public class ConcurrentCooking {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(\"‍ 开始并发做饭...\");
        long startTime = System.currentTimeMillis();
        
        Thread riceThread = new Thread(() -> {
            try {
                System.out.println(\" 开始煮饭\");
                Thread.sleep(5000);
                System.out.println(\" 饭煮好了\");
            } catch (InterruptedException e) {}
        });
        
        Thread soupThread = new Thread(() -> {
            try {
                System.out.println(\" 开始做汤\");
                Thread.sleep(3000);
                System.out.println(\" 汤做好了\");
            } catch (InterruptedException e) {}
        });
        
        Thread dishThread = new Thread(() -> {
            try {
                System.out.println(\" 开始炒菜\");
                Thread.sleep(4000);
                System.out.println(\" 菜炒好了\");
            } catch (InterruptedException e) {}
        });
        
        riceThread.start();
        soupThread.start();
        dishThread.start();
        
        // 等待所有线程完成
        riceThread.join();
        soupThread.join();
        dishThread.join();
        
        long endTime = System.currentTimeMillis();
        System.out.println(\" 并发做饭完成!总时间: \" + (endTime - startTime)/1000 + \"秒\");
    }
}

结果:大约5秒(最慢的任务时间),一个人快速切换任务

1.3 并行:团队协作

生活比喻:专业厨房,厨师、助手、装盘师同时工作

public class ParallelCooking {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(\"‍‍ 开始团队协作做饭...\");
        long startTime = System.currentTimeMillis();
        
        // 创建线程池 - 像组建厨房团队
        ExecutorService kitchenTeam = Executors.newFixedThreadPool(3);
        
        // 分配任务 - 不同的人同时做不同的事
        Future chef = kitchenTeam.submit(() -> {
            System.out.println(\"‍ 大厨开始炒菜\");
            try { Thread.sleep(4000); } catch (InterruptedException e) {}
            System.out.println(\"‍ 大厨:菜炒好了\");
        });
        
        Future assistant = kitchenTeam.submit(() -> {
            System.out.println(\"‍ 助手开始准备配菜\");
            try { Thread.sleep(2000); } catch (InterruptedException e) {}
            System.out.println(\"‍ 助手:配菜准备好了\");
        });
        
        Future server = kitchenTeam.submit(() -> {
            System.out.println(\"‍ 装盘师准备餐具\");
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            System.out.println(\"‍ 装盘师:餐具准备好了\");
        });
        
        // 等待所有任务完成
        chef.get();
        assistant.get();
        server.get();
        
        kitchenTeam.shutdown();
        
        long endTime = System.currentTimeMillis();
        System.out.println(\" 团队协作完成!总时间: \" + (endTime - startTime)/1000 + \"秒\");
    }
}

结果:大约4秒(最慢的任务时间),多人真正同时工作

二、并发问题:当多个人操作同一个东西

2.1 竞态条件:双十一抢购

生活比喻:最后一件商品,你和朋友同时点击\”立即购买\”

public class RaceConditionExample {
    private static int inventory = 1; // 库存只有1件
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println(\" 开始抢购,库存: \" + inventory);
        
        Thread you = new Thread(() -> buy(\"你\"));
        Thread friend = new Thread(() -> buy(\"朋友\"));
        
        you.start();
        friend.start();
        
        you.join();
        friend.join();
        
        System.out.println(\" 最终库存: \" + inventory);
    }
    
    static void buy(String buyer) {
        if (inventory > 0) {
            // 模拟网络延迟
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            
            inventory--; // 库存减1
            System.out.println(\" \" + buyer + \" 抢购成功!\");
        } else {
            System.out.println(\" \" + buyer + \" 抢购失败,库存不足\");
        }
    }
}

可能的结果

 开始抢购,库存: 1
 你 抢购成功!
 朋友 抢购成功!
 最终库存: -1

问题:两个人都成功,但库存变负数!

2.2 解决方案:加锁,像超市收银台排队

public class SafeShopping {
    private static int inventory = 1;
    private static final Object lock = new Object(); // 创建一个锁对象
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println(\" 安全抢购开始,库存: \" + inventory);
        
        Thread you = new Thread(() -> safeBuy(\"你\"));
        Thread friend = new Thread(() -> safeBuy(\"朋友\"));
        
        you.start();
        friend.start();
        
        you.join();
        friend.join();
        
        System.out.println(\" 最终库存: \" + inventory);
    }
    
    static void safeBuy(String buyer) {
        synchronized (lock) { // 一次只让一个人进入这个区域
            if (inventory > 0) {
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                inventory--;
                System.out.println(\" \" + buyer + \" 抢购成功!\");
            } else {
                System.out.println(\" \" + buyer + \" 抢购失败,库存不足\");
            }
        }
    }
}

结果

 安全抢购开始,库存: 1
 你 抢购成功!
 朋友 抢购失败,库存不足
 最终库存: 0

三、可见性问题:公告栏信息不同步

3.1 问题演示:经理发通知,员工看不到

public class VisibilityProblem {
    private static boolean meetingCancelled = false; // 会议取消标志
    
    public static void main(String[] args) throws InterruptedException {
        // 员工线程:不断检查会议是否取消
        Thread employee = new Thread(() -> {
            System.out.println(\"‍ 员工:等待会议开始...\");
            int checkCount = 0;
            
            while (!meetingCancelled) {
                checkCount++;
                // 员工一直在检查...
            }
            
            System.out.println(\"‍ 员工:收到通知,会议取消!检查了 \" + checkCount + \" 次\");
        });
        
        // 经理线程:取消会议
        Thread manager = new Thread(() -> {
            try {
                Thread.sleep(1000); // 经理思考1秒
                meetingCancelled = true;
                System.out.println(\"‍ 经理:会议已取消!\");
            } catch (InterruptedException e) {}
        });
        
        employee.start();
        manager.start();
        
        employee.join();
        manager.join();
    }
}

可能的结果:员工线程永远看不到会议取消!

3.2 解决方案:用大喇叭广播(volatile)

public class VisibilitySolution {
    private static volatile boolean meetingCancelled = false; // 加volatile
    
    public static void main(String[] args) throws InterruptedException {
        Thread employee = new Thread(() -> {
            System.out.println(\"‍ 员工:等待会议开始...\");
            int checkCount = 0;
            
            while (!meetingCancelled) {
                checkCount++;
            }
            
            System.out.println(\"‍ 员工:收到通知,会议取消!检查了 \" + checkCount + \" 次\");
        });
        
        Thread manager = new Thread(() -> {
            try {
                Thread.sleep(1000);
                meetingCancelled = true;
                System.out.println(\"‍ 经理:会议已取消!\");
            } catch (InterruptedException e) {}
        });
        
        employee.start();
        manager.start();
        
        employee.join();
        manager.join();
    }
}

结果:员工能立即看到经理的通知

四、死锁:十字路口堵车

4.1 死锁产生

public class DeadlockExample {
    private static final Object northSouthRoad = new Object(); // 南北向道路
    private static final Object eastWestRoad = new Object();   // 东西向道路
    
    public static void main(String[] args) {
        // 南北方向的车
        Thread northSouthCar = new Thread(() -> {
            synchronized (northSouthRoad) {
                System.out.println(\" 南北车:占用了南北道路\");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                System.out.println(\" 南北车:等待东西道路...\");
                synchronized (eastWestRoad) {
                    System.out.println(\" 南北车:通过十字路口\");
                }
            }
        });
        
        // 东西方向的车  
        Thread eastWestCar = new Thread(() -> {
            synchronized (eastWestRoad) {
                System.out.println(\" 东西车:占用了东西道路\");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                System.out.println(\" 东西车:等待南北道路...\");
                synchronized (northSouthRoad) {
                    System.out.println(\" 东西车:通过十字路口\");
                }
            }
        });
        
        northSouthCar.start();
        eastWestCar.start();
    }
}

结果:两辆车都卡住,谁也过不去!

4.2 死锁解决:统一方向规则

public class DeadlockSolution {
    private static final Object roadA = new Object();
    private static final Object roadB = new Object();
    
    public static void main(String[] args) {
        // 规定:必须先申请roadA,再申请roadB
        Thread car1 = new Thread(() -> {
            synchronized (roadA) {
                System.out.println(\" 车1:占用了道路A\");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                synchronized (roadB) {
                    System.out.println(\" 车1:通过十字路口\");
                }
            }
        });
        
        Thread car2 = new Thread(() -> {
            synchronized (roadA) {  // 同样先申请roadA
                System.out.println(\" 车2:占用了道路A\");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                synchronized (roadB) {
                    System.out.println(\" 车2:通过十字路口\");
                }
            }
        });
        
        car1.start();
        car2.start();
    }
}

五、synchronized 的三种用法

5.1 实例方法同步 – 锁住当前对象

public class BankAccount {
    private int balance = 1000;
    
    // 同步实例方法 - 锁是当前账户对象(this)
    public synchronized void withdraw(String user, int amount) {
        if (balance >= amount) {
            System.out.println(user + \" 开始取款 \" + amount);
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            
            balance -= amount;
            System.out.println(user + \" 取款成功,余额: \" + balance);
        } else {
            System.out.println(user + \" 余额不足\");
        }
    }
}

5.2 静态方法同步 – 锁住整个类

public class Bank {
    private static int totalMoney = 100000;
    
    // 同步静态方法 - 锁是Bank.class
    public static synchronized void audit() {
        System.out.println(\"开始查账...\");
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
        System.out.println(\"查账完成,总资金: \" + totalMoney);
    }
}

5.3 同步代码块 – 灵活控制

public class SmartBankAccount {
    private int balance = 1000;
    private final Object lock = new Object(); // 专门的锁对象
    
    public void transfer(String from, String to, int amount) {
        // 非同步操作
        System.out.println(from + \" 向 \" + to + \" 转账 \" + amount);
        
        // 只同步关键部分
        synchronized (lock) {
            if (balance >= amount) {
                try { Thread.sleep(500); } catch (InterruptedException e) {}
                balance -= amount;
                System.out.println(\"转账成功,余额: \" + balance);
            } else {
                System.out.println(\"余额不足,转账失败\");
            }
        }
        
        // 其他非同步操作
        System.out.println(\"转账操作完成\");
    }
}

六、原子类:内置的\”安全操作\”

6.1 原子计数器

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0); // 原子整数
    
    public void safeIncrement() {
        int newValue = count.incrementAndGet(); // 原子自增
        System.out.println(Thread.currentThread().getName() + \" 增加后: \" + newValue);
    }
    
    public static void main(String[] args) throws InterruptedException {
        AtomicCounter counter = new AtomicCounter();
        
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.safeIncrement();
                }
            });
            threads[i].start();
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
        
        System.out.println(\"最终计数: \" + counter.count.get());
    }
}

七、实战总结:并发编程最佳实践

7.1 简单的计数器对比

public class CounterComparison {
    // 不安全计数器
    private int unsafeCount = 0;
    
    // 安全计数器 - synchronized
    private int safeCount = 0;
    
    // 安全计数器 - 原子类
    private AtomicInteger atomicCount = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        CounterComparison demo = new CounterComparison();
        
        System.out.println(\" 测试不安全计数器:\");
        demo.testUnsafeCounter();
        
        System.out.println(\"n 测试安全计数器(synchronized):\");
        demo.testSafeCounter();
        
        System.out.println(\"n 测试原子计数器:\");
        demo.testAtomicCounter();
    }
    
    void testUnsafeCounter() throws InterruptedException {
        unsafeCount = 0;
        Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) unsafeCount++; });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) unsafeCount++; });
        
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println(\"预期: 20000, 实际: \" + unsafeCount);
    }
    
    void testSafeCounter() throws InterruptedException {
        safeCount = 0;
        Thread t1 = new Thread(() -> { 
            for (int i = 0; i < 10000; i++) {
                synchronized (this) {
                    safeCount++;
                }
            }
        });
        Thread t2 = new Thread(() -> { 
            for (int i = 0; i < 10000; i++) {
                synchronized (this) {
                    safeCount++;
                }
            }
        });
        
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println(\"预期: 20000, 实际: \" + safeCount);
    }
    
    void testAtomicCounter() throws InterruptedException {
        atomicCount.set(0);
        Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) atomicCount.incrementAndGet(); });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) atomicCount.incrementAndGet(); });
        
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println(\"预期: 20000, 实际: \" + atomicCount.get());
    }
}

八、记住这些要点

  1. 并发就像单人杂技:快速切换,看起来同时
  2. 并行就像团队协作:真正同时工作
  3. 锁就像会议室:一次只进一个人
  4. volatile就像大喇叭:一有变化通知所有人
  5. 原子类就像自动售货机:内置安全机制

黄金法则:先保证正确性,再考虑性能。没有正确的并发,再快的速度也是徒劳!

通过这些生活化的比喻和实际的代码示例,相信你对并发编程有了更直观的理解。记住,并发编程的本质就是:在共享的环境中维持秩序

发表评论
暂无评论

还没有评论呢,快来抢沙发~

客服

点击联系客服 点击联系客服

在线时间:09:00-18:00

关注微信公众号

关注微信公众号
客服电话

400-888-8888

客服邮箱 122325244@qq.com

手机

扫描二维码

手机访问本站

扫描二维码
搜索