资讯详情

资讯详情

多线程:安全问题

什么是线程安全问题简单来说当多个线程同时访问共享资源时如果最终的结果取决于线程的执行顺序就会出现线程安全问题。这会导致数据不一致、程序崩溃等不可预知的后果。线程安全问题的根本原因一共享资源竞争操作的非原子性多个线程同时读写同一个共享变量或资源。// 不安全的计数器示例public class UnsafeCounter {private int count 0;public void increment() {count; // 这不是原子操作}public int getCount() {return count;}Thread t1new Thread(()-{for(int i0;i100;i){increment();})Thread t2new Thread(()-{for(int i0;i100;i){increment();})t1.start();t2.start();}看似简单的操作在底层可能是多个步骤的组合。count 的实际执行步骤1. 从内存读取 count 的值到寄存器2. 将寄存器中的值加 13. 将结果写回内存如果多次执行上述代码你会发现count的值并不等于两百而是会出现小于两百的值且不会大于两百这是因为在多个线程针对同一变量直接进行操作是非原子性的。原因如下图可以发现因为操作的非原子性两个线程同时执行countcount却只加了一。二 内存可见性问题由于CPU缓存的存在一个线程对共享变量的修改可能不会立即对其他线程可见。public class VisibilityProblem {private boolean flag true;public void writer() {flag false; // 修改可能不会立即对其他线程可见}public void reader() {while (flag) {// 可能永远循环即使另一个线程已经将flag设为false}}}三 指令重排序编译器和处理器为了优化性能可能会重新排序指令的执行顺序。解决方案详解一使用 synchronized 关键字public class SafeCounter {private int count 0;public synchronized void increment() {count;}public synchronized int getCount() {return count;}}public class SafeCounter {private int count 0;private final Object lock new Object();public void increment() {synchronized(lock) {count;}}}使用锁使操作保持原子性。二使用 volatile 关键字volatile 解决了可见性问题确保变量的修改对所有线程立即可见。public class VolatileExample {private volatile boolean flag true;public void stop() {flag false;}public void work() {while (flag) {// 正常工作}}}volatile 的适用场景- 对变量的写操作不依赖于当前值- 该变量不会与其他状态变量一起参与不变性约束- 访问变量时不需要加锁三使用原子类 (Atomic Classes)Java 提供了一系列原子类使用 CAS(Compare-And-Swap) 操作保证原子性。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();}}常用的原子类- AtomicInteger, AtomicLong, AtomicBoolean- AtomicReferenceT- AtomicIntegerArray, AtomicLongArray四最佳实践和设计原则利用 finall 创建不可变对象。将对象限制在单个线程中使用避免共享。- 优先使用不可变对象- 尽量减少共享状态- 避免过度同步
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →