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 更安全:
- 类型安全 -
Mutex<T>
保护特定类型的数据 - 作用域自动释放 - 离开作用域自动解锁
- 编译期检查 - 不会忘记获取锁
通道(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 的世界里,函数式编程不是性能的敌人,而是性能的朋友。