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

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

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

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

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

java 复制代码
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 并发:一个人轮流做多件事

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

java 复制代码
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 并行:团队协作

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

java 复制代码
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 竞态条件:双十一抢购

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

java 复制代码
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 解决方案:加锁,像超市收银台排队

java 复制代码
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 问题演示:经理发通知,员工看不到

java 复制代码
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)

java 复制代码
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 死锁产生

java 复制代码
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 死锁解决:统一方向规则

java 复制代码
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 实例方法同步 - 锁住当前对象

java 复制代码
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 静态方法同步 - 锁住整个类

java 复制代码
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 同步代码块 - 灵活控制

java 复制代码
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 原子计数器

java 复制代码
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 简单的计数器对比

java 复制代码
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. 原子类就像自动售货机:内置安全机制

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

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

相关推荐
Ray663 小时前
client
后端
苏三的开发日记3 小时前
RocketMQ面试题
后端
SimonKing3 小时前
【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(六)!
java·后端·程序员
xiaoye20183 小时前
mybatis-plus 浅析
后端
qincloudshaw3 小时前
java中实现对象深克隆的四种方式
后端
caimo3 小时前
Java无法访问网址出现Timeout但是浏览器和Postman可以
java·开发语言·postman
Deamon Tree4 小时前
ElasticSearch架构和写入、更新、删除、查询的底层逻辑
java·大数据·elasticsearch·架构
代码哈士奇4 小时前
简单使用Nest+Nacos+Kafka实现微服务
后端·微服务·nacos·kafka·nestjs
一 乐4 小时前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·商城推荐系统