第 13 章:Java 并发编程的噩梦经历过吗?

synchronized 的性能陷阱、volatile 的语义混乱、ConcurrentModificationException 的突然袭击、死锁调试的绝望时刻…

这些痛苦,Rust 要一次性终结。

Rust 的承诺:无畏并发(Fearless Concurrency)。不是因为并发变简单了,而是因为编译器不允许你犯错。

准备好体验真正的并发安全了吗?

Java 并发:运行时的赌博游戏

数据竞争:隐形的杀手

// Java 的经典灾难
public class Counter {
    private int count = 0;

    public void increment() {
        count++;  // 看似简单,实际上包含三个操作:读-改-写
    }

    public int get() {
        return count;  // 在多线程环境下,返回值不可预测
    }
}

// 多线程使用
Counter counter = new Counter();
// 1000个线程各自增加1000次
// 期望结果:1000000
// 实际结果????? (永远猜不到)

问题在哪?编译器不知道,运行时才暴露。

Java 的并发工具:复杂且易错

// synchronized:性能杀手
public synchronized void increment() {
    count++;  // 整个方法都被锁住
}

// ReentrantLock:使用复杂
private final ReentrantLock lock = new ReentrantLock();

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();  // 忘记解锁?死锁等着你
    }
}

// volatile:语义混乱
private volatile int count = 0;  // 只保证可见性不保证原子性

每种工具都有陷阱,每个陷阱都是运行时炸弹。

Rust 并发:编译期的安全保证

所有权系统:天然的并发安全

use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    let handle = thread::spawn(|| {
        println!("{:?}", data);  // 编译错误!
    });

    println!("{:?}", data);  // data 可能被两个线程同时访问

    handle.join().unwrap();
}

编译器直接拒绝: data 不能在两个线程间共享,因为所有权规则不允许。

修复方案:转移所有权

use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    let handle = thread::spawn(move || {  // move 关键字
        println!("{:?}", data);  // data 的所有权转移到新线程
    });

    // println!("{:?}", data);  // 编译错误!data 已经被move

    handle.join().unwrap();
}

看到了吗?Rust 在编译期就阻止了数据竞争。

Arc:原子引用计数

想要多个线程共享数据?使用 Arc<T>(Atomic Reference Counted):

use std::thread;
use std::sync::Arc;

fn main() {
    let data = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let data = Arc::clone(&data);  // 增加引用计数
        let handle = thread::spawn(move || {
            println!("Thread {}: {:?}", i, data);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

Arc 保证了内存安全,但数据是只读的。 想要修改数据?需要更多工具。

Mutex:互斥锁的正确用法

use std::thread;
use std::sync::{Arc, Mutex};

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });  // 锁在这里自动释放!
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());  // 一定是 10
}

Rust 的 Mutex 比 Java 的 synchronized 更安全:

  1. 类型安全 - Mutex<T> 保护特定类型的数据
  2. 作用域自动释放 - 离开作用域自动解锁
  3. 编译期检查 - 不会忘记获取锁

通道(Channel):消息传递的艺术

use std::thread;
use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
        // println!("{}", val);  // 编译错误!val 已被 send 消费
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

所有权系统保证:

  • 发送后数据所有权转移
  • 接收方获得数据所有权
  • 不可能出现数据竞争

这就是 “消息传递代替共享内存” 的哲学。

性能对比:Rust vs Java

Java 的并发成本

// synchronized 的重量级锁
public synchronized void increment() {
    count++;  // 系统调用,上下文切换,性能损失
}

// volatile 的内存屏障
private volatile int flag;  // 每次访问都刷新缓存

Rust 的零成本并发

// Arc:原子操作,无锁竞争时的最优性能
let data = Arc::new(42);

// Mutex:系统级互斥锁,但使用更安全
let mutex = Mutex::new(0);

// Channel:无锁队列实现,高性能消息传递
let (tx, rx) = mpsc::channel();

Rust 的并发原语在保证安全的前提下,追求最优性能。

实战案例:生产者-消费者模型

Java 版本:复杂且易错

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 10;
    private final Object lock = new Object();

    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (lock) {
                while (queue.size() == CAPACITY) {
                    lock.wait();  // 容易死锁
                }
                queue.add(value++);
                lock.notifyAll();  // 唤醒所有等待线程,效率低
            }
            Thread.sleep(100);
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (lock) {
                while (queue.isEmpty()) {
                    lock.wait();
                }
                int value = queue.poll();
                System.out.println("Consumed: " + value);
                lock.notifyAll();
            }
            Thread.sleep(150);
        }
    }
}

Rust 版本:简洁且安全

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    // 生产者
    let producer = thread::spawn(move || {
        for i in 0..10 {
            tx.send(i).unwrap();
            println!("Produced: {}", i);
            thread::sleep(Duration::from_millis(100));
        }
    });

    // 消费者
    let consumer = thread::spawn(move || {
        while let Ok(value) = rx.recv() {
            println!("Consumed: {}", value);
            thread::sleep(Duration::from_millis(150));
        }
    });

    producer.join().unwrap();
    consumer.join().unwrap();
}

看到差距了吗?Rust 版本更简洁、更安全、更易懂。

Send 和 Sync:编译期的并发契约

Send Trait:可安全转移的类型

// 实现了 Send 的类型可以在线程间转移所有权
fn spawn_thread<T: Send + 'static>(data: T) {
    thread::spawn(move || {
        // 使用 data
    });
}

// 大多数类型都实现了 Send
spawn_thread(42);              // i32: Send
spawn_thread("hello".to_string()); // String: Send
spawn_thread(vec![1, 2, 3]);   // Vec<T>: Send (如果 T: Send)

// 但有些类型不是 Send
// spawn_thread(Rc::new(42));  // 编译错误!Rc<T> 不是 Send

Sync Trait:可安全共享引用的类型

// 实现了 Sync 的类型可以在线程间共享引用
fn share_reference<T: Sync>(data: &T) {
    let data_ref = data;
    thread::spawn(move || {
        // 使用 data_ref
    });
}

// 大多数不可变类型都是 Sync
let x = 42;
share_reference(&x);  // &i32 是 Sync

// 但可变引用不能直接共享
// let mut y = 42;
// share_reference(&y);  // 编译错误!需要同步原语

Send 和 Sync 是编译器对并发安全的"数学证明"。

无锁数据结构:性能的极致

use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    let counter = AtomicUsize::new(0);
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = &counter;
        let handle = thread::spawn(move || {
            for _ in 0..1000 {
                counter.fetch_add(1, Ordering::SeqCst);  // 原子操作
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final count: {}", counter.load(Ordering::SeqCst));  // 10000
}

原子操作提供最高性能的并发安全。

写在最后:并发的哲学革命

Java 并发的哲学:给你工具,祈祷你正确使用。

Rust 并发的哲学:编译器保证你不会犯错。

这种保证带来的收益:

  • 编译期安全 - 数据竞争不可能存在
  • 性能优化 - 零成本抽象,最优并发原语
  • 开发信心 - 编译通过就是线程安全
  • 调试简化 - 并发 bug 在编译期就被发现

当你掌握了 Rust 的并发模型,你会发现:

  • 不再害怕多线程编程
  • 不再需要复杂的同步工具
  • 不再担心隐藏的并发 bug
  • 性能还比以前更好

这就是无畏并发的威力:让并发编程变成编译期的数学问题,而不是运行时的碰运气游戏。

下一章我们要学习 Rust 的迭代器和闭包。准备好体验函数式编程的优雅了吗?

因为在 Rust 的世界里,函数式编程不是性能的敌人,而是性能的朋友。