哈哈,小白你好!今天咱们来聊聊Java里的“线程安全”——这玩意儿就像一群程序员在办公室里抢同一份披萨,如果没点规矩,最后可能谁都吃不上完整的,还弄一地碎屑!😄
简单说,线程安全就是当多个“线程”(你可以想象成一群小工人)同时操作同一个东西(比如一个共享变量)时,保证数据不乱套、不出错。Java里,如果代码没处理好线程安全,就会出现“竞态条件”(race condition),就像两个线程同时去改一个计数器,结果数字跳来跳去,比股票市场还刺激!
下面我带你一步步玩转线程安全,先来个“反面教材”,再给个“安全方案”。代码我都会详细解释,包你一看就懂!
1. 反面教材:线程不安全计数器
想象一下,有个计数器类,多个线程同时去增加它。如果不加防护,结果可能乱成一锅粥。来,看看代码:
public class UnsafeCounter {
private int count = 0; // 共享变量,危险地带!
// 增加计数器的方法
public void increment() {
count++; // 这行代码不是原子操作,多个线程同时执行会出问题
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
UnsafeCounter counter = new UnsafeCounter();
// 创建两个线程,每个线程增加计数器1000次
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
// 启动线程
thread1.start();
thread2.start();
// 等待线程结束
thread1.join();
thread2.join();
// 理论上应该输出2000,但实际可能小于2000
System.out.println("最终计数: " + counter.getCount()); // 输出可能像1998或1995,乱套了!
}
}
问题解释:
count++这行代码看着简单,但背后是三个步骤:读取当前值、加1、写回新值。- 如果线程A和线程B同时读取(比如都读到100),然后都加1(变成101),再写回,结果计数器只增加了1次,而不是2次!这就是竞态条件。
- 运行几次,你会看到输出值五花八门,绝对不到2000,证明线程不安全。
2. 安全方案:使用synchronized加锁
现在,咱们给计数器加个“门锁”,就像办公室的披萨只能一个人切一样。Java的synchronized关键字就是这把锁,保证同一时间只有一个线程能操作共享资源。
public class SafeCounter {
private int count = 0;
// 用synchronized修饰方法,保证同一时间只有一个线程执行这个方法
public synchronized void increment() {
count++; // 现在安全了!
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SafeCounter counter = new SafeCounter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终计数: " + counter.getCount()); // 稳稳输出2000,完美!
}
}
安全解释:
synchronized关键字在方法上加了锁。当一个线程执行increment()时,其他线程必须等待,避免了并发冲突。- 现在
count++是原子操作了(在锁的保护下),结果总是2000,线程安全达成! - 注意:锁会增加开销,但为了数据一致性,这点代价值了。
3. 进阶玩法:使用AtomicInteger
如果你嫌锁太重,Java还提供了更轻量的工具,比如AtomicInteger,它用硬件级指令保证原子性,速度快得像闪电!
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
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 {
AtomicCounter counter = new AtomicCounter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终计数: " + counter.getCount()); // 还是2000,但效率更高!
}
}
优势解释:
AtomicInteger的incrementAndGet()方法是原子的,底层用CAS(Compare-And-Swap)实现,比锁更高效。- 适合高并发场景,比如Web服务器计数访问量。
总结
小白们,记住啦:在多线程世界,共享资源就像公共厕所——不加锁谁都能进,但出来可能一团糟!😜 用synchronized或原子类,就能避免数据打架。多练练代码,你会爱上线程安全的优雅!如果有问题,随时来问,咱们一起搞定Java的“披萨难题”!🍕
2068

被折叠的 条评论
为什么被折叠?



