面试直击:并发编程三要素+线程安全全攻略!

182 阅读4分钟



Hi,大家好,我是小米,一个喜欢分享技术的程序员大哥哥!最近有个学弟来找我,说在面试某大厂Java岗位时被问了个问题:“并发编程三要素是什么?在Java中如何保证多线程运行的安全?”学弟被问懵了,不知道怎么答。没关系!今天咱们就来聊聊这个经典问题!

并发编程三要素是什么?

在多线程编程中,最重要的三大核心要素是 原子性可见性有序性。这三个概念是并发编程的基础,掌握它们可以帮助我们更好地理解线程安全问题。

1. 原子性(Atomicity)

原子性指的是一个操作要么完全执行,要么完全不执行,不会出现中间状态。

比如,银行转账时,假设 A 给 B 转100块,分两步:

  • 从 A 的账户扣 100 块;
  • 往 B 的账户加 100 块。

如果这两个操作中间被打断,导致只执行了第一个操作,钱就“丢”了。所以,我们需要保证整个转账操作的原子性

在 Java 中,synchronizedLock 是保证原子性的重要工具。

2. 可见性(Visibility)

可见性指的是当一个线程修改了共享变量的值,其他线程能够及时知道这个修改。

Java 中每个线程都有自己的工作内存,当线程 A 修改了一个变量,线程 B 并不知道,因为 B 可能还在用它的旧值。这种情况下就会发生数据不一致的问题。

为了解决可见性问题,Java 提供了 volatile 关键字,以及 synchronizedLock 机制。

3. 有序性(Orderliness)

有序性指的是程序的执行顺序按照代码的书写顺序来运行。

不过,在并发编程中,编译器和处理器可能会为了优化性能而重排序,导致程序执行的顺序与代码顺序不同。

Java 提供了 volatilesynchronized内存屏障 来避免重排序问题。

在 Java 中如何保证多线程运行安全?

多线程的安全问题可以概括为一句话:保证并发访问的共享资源在任何时候都是一致的。接下来,我们看看几种保证线程安全的方式。

1. 使用 synchronized

synchronized 是 Java 提供的最基础的锁机制,用于保证同一时刻只有一个线程可以访问某段代码。比如:

上面的 withdraw 和 getBalance 方法都被 synchronized 修饰,保证了对 balance 的访问是线程安全的。

2. 使用 Lock

Java 的 java.util.concurrent.locks 包提供了更灵活的锁机制,比如 ReentrantLock。它和 synchronized 的区别在于:

  • 可以尝试加锁(tryLock);
  • 可以定时加锁;
  • 支持中断。

示例代码:

3. 使用 volatile

当我们只需要保证可见性而不是原子性时,可以使用 volatile。例如:

volatile 确保 running 的修改对其他线程是可见的。

4. 使用 原子类

Java 提供了一系列原子类(AtomicInteger, AtomicLong, 等),用于保证操作的原子性。例如:

这里的 incrementAndGet 方法是线程安全的。

5. 使用线程安全的集合

在并发环境下操作集合时,可以使用 java.util.concurrent 包下的线程安全集合类,比如:

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • BlockingQueue

示例代码:

这些集合类在内部已经实现了线程安全机制,避免了手动加锁的麻烦。

总结

并发编程三要素

  • 原子性 - 保证操作不可分割。
  • 可见性 - 保证线程间的修改是可见的。
  • 有序性 - 保证代码按预期顺序执行。

多线程安全的解决方案

  • 使用 synchronized 或 Lock 来控制线程访问;
  • 使用 volatile 解决可见性问题;
  • 使用原子类处理简单的原子性操作;
  • 使用线程安全的集合类简化开发。

END

希望通过这篇文章,大家能更好地理解并发编程三要素以及如何用 Java 编写线程安全的程序。如果觉得有收获,不要忘了点个赞或者转发给身边的朋友哦!有任何问题,也欢迎在评论区留言,我们一起探讨~

我是小米,咱们下次见啦!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!