找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

211

积分

0

好友

23

主题
发表于 前天 00:59 | 查看: 8| 回复: 0

多个线程同时访问时,如果不需要额外的同步就能正确工作,那就是线程安全的——这就像一家和谐的餐厅,多位厨师共享厨房却不会互相干扰。

一、什么是线程安全?从餐厅厨房说起

想象一家繁忙的餐厅厨房,多位厨师(线程)共享使用有限的厨具(共享资源)和食材(数据)。如果没有合理规则,可能会发生:

  • 两位厨师同时争抢同一把刀(资源竞争)
  • 一位厨师刚判断汤里需要加盐,另一位却把盐用光了(竞态条件)
  • 一位厨师更新了菜单,但其他厨师仍按旧菜单准备(内存可见性问题)

Java中,一个简单的示例可以说明问题:

public class UnsafeCounter {
    private int count = 0;

    public void increment() {
        count++;  // 这不是原子操作!
    }
}

这个简单的count++操作实际上包含三个步骤:读取当前值、增加1、写回新值。当多线程同时执行时,可能会发生数据丢失现象。

二、线程安全问题的根源:计算机底层视角

1. 内存可见性问题:不只是"看不见"那么简单

现代计算机架构中,每个CPU都有自己的缓存。当一个线程修改了共享变量,该修改可能暂时只存在于当前CPU的缓存中,不会立即写回主内存,其他线程也就无法立即看到这个变化。

public class VisibilityProblem {
    private static boolean flag = false; // 缺少volatile关键字

    public static void main(String[] args) {
        Thread writer = new Thread(() -> {
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            flag = true; // 修改可能不会立即对其他线程可见
        });

        Thread reader = new Thread(() -> {
            while (!flag) {
                // 可能永远循环,看不到flag的变化
            }
        });

        writer.start();
        reader.start();
    }
}

2. 竞态条件:像"抢购限量商品"

竞态条件就像多人同时抢购最后一件商品:A看到有库存,B也看到有库存,但只有一人能成功购买。

public class RaceCondition {
    private int balance = 100;

    // 不安全的取款方法
    public void withdraw(int amount) {
        if (balance >= amount) {
            // 如果在这里线程被切换,可能导致超额取款
            balance -= amount;
        }
    }
}

三、Java中的线程安全解决方案

1. 内置锁(synchronized):厨房的"专用令牌"

synchronized关键字就像厨房的专用令牌,只有拿到令牌的厨师才能使用特定厨具。

public class SafeCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;  // 现在安全了!
    }
}

底层原理:synchronized基于监视器锁(Monitor)实现,每个Java对象都有一个内置锁。线程进入同步代码前自动获取锁,退出时自动释放锁。

2. volatile关键字:餐厅的"中央公告板"

volatile确保变量的修改立即对其他线程可见,就像餐厅的中央公告板,任何更新都会立即被所有人看到。

public class VisibleFlag {
    private volatile boolean stopRequested = false;

    public void stop() {
        stopRequested = true;  // 修改立即对所有线程可见
    }
}

但注意:volatile不保证复合操作的原子性,它只解决可见性问题。

3. 原子类:无锁的"智能计数器"

Java的java.util.concurrent.atomic包提供了一系列原子类,如AtomicInteger,它们使用CAS(Compare-And-Swap)指令实现,无需锁也能保证原子性。

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

    public void increment() {
        count.incrementAndGet();  // 原子操作,性能比synchronized更高
    }
}

4. 并发集合:线程安全的"共享储物柜"

Java提供了多种线程安全的并发集合类:

  • ConcurrentHashMap:支持高并发的HashMap实现
  • CopyOnWriteArrayList:读多写少场景的理想选择
  • BlockingQueue:优秀的生产者-消费者实现工具

四、实战场景:如何选择正确的线程安全策略

场景1:计数器(高频更新)

// 推荐:AtomicLong(性能最佳)
private AtomicLong requestCount = new AtomicLong();

// 次选:synchronized(保证安全但性能较低)
private long requestCount = 0;
public synchronized void increment() { requestCount++; }

场景2:缓存(读多写少)

// 推荐:ConcurrentHashMap(并发读写性能均衡)
private ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

// 特殊情况:CopyOnWriteArrayList(读极多,写极少)
private CopyOnWriteArrayList<String> configList = new CopyOnWriteArrayList<>();

场景3:状态标志(简单状态控制)

// 推荐:volatile(简单可见性保证)
private volatile boolean shutdownRequested = false;

// 不推荐:AtomicBoolean(过度复杂,volatile已足够)

五、线程安全的级别:从"不可变"到"线程对立"

根据线程安全程度,我们可以将类分为几个级别:

  1. 不可变(Immutable):像String、Long这样的类,状态创建后就不能改变,天生线程安全。
  2. 无条件的线程安全:如ConcurrentHashMap,有足够的内部同步,无需外部同步。
  3. 有条件的线程安全:如Collections.synchronizedList返回的集合,迭代时需要外部同步。
  4. 非线程安全:如ArrayList、HashMap,需要客户端自己实现同步。
  5. 线程对立:即使外部同步,也无法保证线程安全(应避免)。

六、线程安全的最佳实践

  1. 优先使用不可变对象:不可变对象天生线程安全,是解决并发问题的最佳选择。
  2. 文档化线程安全保证:在代码文档中明确说明类的线程安全级别。
  3. 避免过度同步:同步范围过大可能导致性能问题甚至死锁。
  4. 谨慎使用公共锁对象:考虑使用私有锁对象防止拒绝服务攻击。
public class PrivateLock {
    private final Object lock = new Object();  // 私有锁对象

    public void safeMethod() {
        synchronized(lock) {  // 外部无法干扰
            // 安全操作
        }
    }
}

七、总结:线程安全的"终极秘诀"

线程安全不是魔法,而是建立在三个基石上:

  1. 原子性:操作要么完全执行,要么完全不执行
  2. 可见性:一个线程的修改对其他线程立即可见
  3. 有序性:程序按代码顺序执行(允许必要的重排序优化)

回到餐厅厨房的比喻,确保线程安全就像制定良好的厨房工作规则:为关键区域设立专用令牌(synchronized),设置中央公告板及时通知变化(volatile),以及建立明确的工作流程(原子操作)。

最重要的是,在编写并发代码时,不要依赖猜测,而要基于可靠的并发工具和明确的约定。多线程编程虽然复杂,但掌握了正确的方法和工具,我们就能编写出既安全又高效的程序。

参考文章

  1. https://www.51cto.com/article/627460.html
  2. https://blog.csdn.net/u013773608/article/details/99752973
  3. https://blog.csdn.net/Coloured_Glaze/article/details/100635585
  4. https://blog.csdn.net/weixin_33893473/article/details/92415650
  5. https://blog.csdn.net/2301_78064339/article/details/131021135
  6. https://my.oschina.net/emacs_8710921/blog/17077058
  7. https://my.oschina.net/emacs_9455642/blog/18592766
  8. https://blog.csdn.net/zhxup606/article/details/151683489
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-1 14:12 , Processed in 0.061458 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表