简易理解Java中的并发、并行、多线程相关知识

82 阅读5分钟

故事场景:餐厅服务系统

假设你经营一家餐厅,顾客们来用餐需要点餐、做菜、上菜。随着生意越来越好,你需要优化餐厅的服务流程,这就涉及到 Java 并发编程的各种概念。

1. 并发模型(Concurrency Model)

故事
你最初采用 "单厨师" 模式:一个厨师同时处理所有订单,做完一道菜再做下一道。但顾客抱怨等待时间太长。
于是你改为 "多厨师" 模式:多个厨师同时做菜,大大提高了效率。

Java 类比

  • 单线程模型:所有任务按顺序执行(如单个厨师)。

  • 多线程模型:多个任务同时执行(如多个厨师)。

  • 并行 vs 并发:并行是真正的同时执行(多核 CPU),并发是交替执行(单核 CPU 切换线程)。

代码示例

java

// 单线程模型
public class SingleThreadModel {
    public static void main(String[] args) {
        cook("宫保鸡丁");
        cook("鱼香肉丝");
    }

    private static void cook(String dish) {
        System.out.println("开始做:" + dish);
        try {
            Thread.sleep(1000); // 模拟做菜时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(dish + "做好了");
    }
}

// 多线程模型
public class MultiThreadModel {
    public static void main(String[] args) {
        new Thread(() -> cook("宫保鸡丁")).start();
        new Thread(() -> cook("鱼香肉丝")).start();
    }

    private static void cook(String dish) {
        System.out.println("厨师开始做:" + dish);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(dish + "做好了");
    }
}

使用场景

  • 单线程:任务顺序执行,如简单脚本。
  • 多线程:IO 密集型任务(如网络请求、文件读写)、并行计算。

2. 锁(Lock)

故事
餐厅只有一个搅拌机,两个厨师需要用它。如果同时抢,可能会弄坏机器。于是你规定:使用前必须先拿到 "搅拌机钥匙",用完再还。

Java 类比

  • synchronized 关键字:内置锁,同一时间只有一个线程可以执行同步代码块。

  • ReentrantLock:显式锁,功能更强大(可中断、可定时、公平锁)。

代码示例

java

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Blender {
    private final Lock lock = new ReentrantLock();

    public void useBlender(String chef) {
        lock.lock(); // 获取锁
        try {
            System.out.println(chef + "拿到了搅拌机");
            Thread.sleep(1000);
            System.out.println(chef + "用完了搅拌机");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        Blender blender = new Blender();
        new Thread(() -> blender.useBlender("厨师A")).start();
        new Thread(() -> blender.useBlender("厨师B")).start();
    }
}

使用场景

  • 多线程共享资源(如账户转账、计数器)。
  • 保证原子性操作(如 i++)。

3. CAS(Compare-And-Swap)

故事
餐厅有个 "今日特价菜" 牌子,服务员可以更新它。为避免冲突,每次更新时需要确认:"我看到的价格是 X,如果是,就改成 Y"。

Java 类比

  • CAS 操作:原子性地比较并更新值。

  • Unsafe 类:Java 底层提供的 CAS 操作(如compareAndSwapInt)。

代码示例

java

import java.util.concurrent.atomic.AtomicInteger;

public class SpecialDish {
    private AtomicInteger price = new AtomicInteger(100); // 初始价格100元

    public void updatePrice(int expectedPrice, int newPrice) {
        // CAS操作:如果当前价格是expectedPrice,则更新为newPrice
        boolean success = price.compareAndSet(expectedPrice, newPrice);
        System.out.println("更新价格:" + (success ? "成功" : "失败"));
    }

    public static void main(String[] args) {
        SpecialDish dish = new SpecialDish();
        new Thread(() -> dish.updatePrice(100, 90)).start(); // 尝试将100改为90
        new Thread(() -> dish.updatePrice(100, 80)).start(); // 尝试将100改为80
    }
}

使用场景

  • 高并发下的无锁算法(如 ConcurrentHashMap)。
  • 替代重量级锁,提高性能。

4. 原子变量(Atomic Variables)

故事
餐厅有个 "今日客流量" 计数器,多个服务员需要同时增加计数。你买了一个特殊计数器,它的 ++ 操作是原子性的,不会出错。

Java 类比

  • AtomicIntegerAtomicLong等:基于 CAS 实现的原子变量。

代码示例

java

import java.util.concurrent.atomic.AtomicInteger;

public class CustomerCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // 原子性自增
    }

    public int getCount() {
        return count.get();
    }

    public static void main(String[] args) throws InterruptedException {
        CustomerCounter counter = new CustomerCounter();
        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];

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

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

        System.out.println("最终客流量:" + counter.getCount()); // 输出10000,不会出错
    }
}

使用场景

  • 计数器、序列号生成器。
  • 替代synchronized实现轻量级同步。

5. 线程池(ThreadPool)

故事
餐厅生意太好,每次来顾客都临时招厨师,做完菜就解雇,成本太高。于是你雇了固定数量的厨师团队(线程池),顾客来了直接分配厨师,无需等待招聘。

Java 类比

  • ExecutorService:线程池接口。

  • Executors:工具类,提供创建线程池的静态方法。

代码示例

java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RestaurantThreadPool {
    public static void main(String[] args) {
        // 创建固定大小的线程池(3个厨师)
        ExecutorService pool = Executors.newFixedThreadPool(3);

        // 模拟10个顾客订单
        for (int i = 1; i <= 10; i++) {
            final int orderId = i;
            pool.submit(() -> {
                System.out.println("厨师开始处理订单:" + orderId);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("订单" + orderId + "处理完成");
            });
        }

        pool.shutdown(); // 关闭线程池
    }
}

使用场景

  • 大量短期任务(如 Web 服务器处理请求)。
  • 控制并发线程数量,避免资源耗尽。

6. AQS(AbstractQueuedSynchronizer)

故事
餐厅的 "VIP 包间" 只有一个,顾客需要排队等待。你设计了一个排队系统:

  1. 顾客到达后先尝试进入包间(CAS 操作)。

  2. 如果包间被占用,顾客进入等待队列(双向链表)。

  3. 包间释放时,通知队列中的下一位顾客。

Java 类比

  • AQS:Java 并发包的基础框架,提供锁和同步器的实现模板。

  • ReentrantLockCountDownLatch等都基于 AQS 实现。

代码示例

java

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

// 自定义同步器(模拟VIP包间)
class VipRoomSync extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int acquires) {
        if (compareAndSetState(0, 1)) { // CAS操作:0表示空闲,1表示已占用
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int releases) {
        setExclusiveOwnerThread(null);
        setState(0); // 释放资源
        return true;
    }
}

public class VipRoom {
    private final VipRoomSync sync = new VipRoomSync();

    public void enter() {
        sync.acquire(1);
        System.out.println(Thread.currentThread().getName() + "进入VIP包间");
    }

    public void leave() {
        System.out.println(Thread.currentThread().getName() + "离开VIP包间");
        sync.release(1);
    }

    public static void main(String[] args) {
        VipRoom room = new VipRoom();

        for (int i = 1; i <= 5; i++) {
            final int customerId = i;
            new Thread(() -> {
                room.enter();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                room.leave();
            }, "顾客" + customerId).start();
        }
    }
}

使用场景

  • 自定义同步器(如读写锁、信号量)。
  • 实现复杂的并发控制逻辑。

总结

概念故事类比Java 实现使用场景
并发模型单厨师 vs 多厨师单线程 vs 多线程任务顺序执行 vs 并行处理
搅拌机钥匙synchronized, ReentrantLock共享资源保护
CAS特价菜更新确认Unsafe.compareAndSwapInt无锁算法、高性能计数器
原子变量特殊计数器AtomicInteger, AtomicLong计数器、序列号生成器
线程池固定厨师团队ExecutorService大量短期任务、控制并发数
AQSVIP 包间排队系统AbstractQueuedSynchronizer自定义同步器、复杂并发控制

通过餐厅的例子,你可以把 Java 并发编程的各种概念想象成餐厅管理的策略,每种策略都解决了特定的效率或正确性问题。