一、揭秘Java内存模型(JMM)
是否曾对Java的并发编程感到困惑?是否觉得多线程下的数据同步和一致性难以捉摸?今天,我们就来揭开Java内存模型(JMM)的神秘面纱,让你对并发编程有更深入的理解!
1、什么是Java内存模型(JMM)?
简单来说,Java内存模型(JMM)是Java虚拟机(JVM)规范中定义的一种内存访问模型,它决定了一个线程对共享变量的写入何时以及如何变成对另一个线程可见。在并发编程中,JMM保证了数据的一致性和正确性,是Java并发编程的基石。
2、JMM的三大特性
原子性:一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。在Java中,基本数据类型的访问和赋值都是原子性的,但复合操作(如i++)则不是。
可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步到主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来保证多线程之间的可见性的。
有序性程序执行的顺序按照代码的先后顺序执行(处理器可能会对指令进行重排序)。Java内存模型通过禁止一定类型的编译器重排序和处理器重排序来向程序员提供顺序一致性保证。
后续专题会针对原子性、可见性、有序向三个问题产生的原因,以及对应的解决方法详细的解析
3、如何保证JMM的三大特性?
volatile关键字:volatile关键字保证了可见性和一定程度的有序性。当一个变量被volatile修饰时,它会保证修改的值会立即被更新到主内存,当有其他线程需要读取时,它会去主内存中读取新值。同时,volatile关键字会禁止指令重排序优化。
synchronized关键字:synchronized关键字保证了原子性、可见性和有序性。当一个线程访问某个对象的某个synchronized(this)同步代码块时,其他试图访问该对象的所有其他类型的同步代码块的线程将会被阻塞,直到第一个线程退出同步块为止。同时,synchronized关键字也会强制将修改的值立即写入主内存,并从主内存中读取值。
Lock接口:Lock接口是Java 5中引入的,提供了比synchronized更强大的锁功能。Lock接口提供了更多的灵活性,比如尝试获取锁(tryLock())、定时获取锁(tryLock(long timeout, TimeUnit unit))、可中断地获取锁(lockInterruptibly())等。同时,Lock接口也保证了原子性、可见性和有序性。
二、重要知识点
💡 JMM的三大特性:原子性、可见性、有序性
- 原子性:操作的不可分割
* 定义:一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
* 问题:如果没有原子性保障,线程间可能会产生数据冲突和不一致。
- 可见性:及时感知变量更新
* 定义:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
* 问题:如果可见性得不到保证,线程可能会读取到旧值,导致逻辑错误。
- 有序性:执行顺序的一致性
* 定义:在并发环境下,指令的执行顺序可能受到各种因素的影响而发生变化。
* 问题:有序性问题可能会导致线程之间的协作出现混乱,程序运行结果不可预期。
🔍 常见问题与解析
- 如何保证原子性?
* 答案:使用synchronized
关键字、Lock
接口、Atomic
类等工具来保证操作的原子性。
- volatile如何实现可见性?
* 答案:volatile关键字会确保每次读取前都刷新缓存,从而确保读取的是主内存的最新值;每次写入后都会立即刷新到主内存,从而确保其他线程可以读取到最新值。
- happens-before规则是什么?
* 答案:JMM定义了一系列规则,当满足这些规则时,一个操作的结果将对另一个操作可见。这些规则包括程序顺序规则、监视器锁规则、volatile变量规则等。
三、总结提升
🔍 架构师的JMM视角
- 原子性与非阻塞:追求高效的并发
JMM中的原子性操作保证了操作的不可分割性,避免了多线程同时操作一个数据而导致的混乱。同时,非阻塞算法如CAS(Compare-And-Swap)也在JMM中得到了广泛应用,它们能够在不阻塞其他线程的情况下实现数据更新。这启示我们在架构设计中要追求高效的并发性能,通过合理的算法和同步机制来减少线程间的竞争和等待。
- 有序性与内存屏障:掌控执行顺序
JMM的有序性规则定义了哪些操作之间的内存可见性,它允许编译器和处理器对指令进行重排序以提高性能。然而,这也可能导致程序出现意料之外的行为。为了解决这个问题,JMM引入了内存屏障来禁止某些类型的重排序。这启示我们在架构设计中要关注指令的执行顺序,通过合理的内存屏障来确保程序的正确性和性能。
四、思考题:
题目:
在Java中,我们知道volatile
关键字保证了变量的可见性和有序性(在一定程度上),但它并不保证原子性。请设计一个场景,其中使用volatile
关键字的代码在并发环境下出现了预期之外的行为,并解释原因。然后,提出一个修改方案来确保该场景下的并发安全性。
假设我们有一个计数器类Counter
,它使用volatile
关键字修饰了一个long
类型的计数器变量count
。我们有两个线程同时对这个计数器进行自增操作。
public class Counter {
private volatile long count = 0;
public void increment() {
• count++; // 非原子操作
}
public long getCount() {
• return count;
}
}
原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/87749.html