首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 02 | Java内存模型:看Java如何解决可见性和有序性问题 在深入探索Java并发编程的广阔领域时,理解Java内存模型(Java Memory Model, JMM)是不可或缺的一环。JMM不仅定义了Java程序中各种变量(包括实例字段、静态字段和构成数组对象的元素)的访问规则,还直接关联到并发编程中的两大核心挑战:可见性(Visibility)和有序性(Ordering)问题。本章将详细解析Java内存模型,并探讨它是如何巧妙地解决这些并发难题的。 #### 一、Java内存模型概述 Java内存模型是一种规范,它描述了Java程序中各个变量(包括实例变量、静态变量和数组元素)的访问方式。在并发编程环境中,由于多个线程可能同时访问共享变量,而这些访问操作在物理上可能由不同的处理器执行,因此直接导致了内存访问的不一致性问题。Java内存模型通过定义一系列规则,确保了在多线程环境下,对共享变量的操作能够以一致、可预测的方式执行。 #### 二、可见性问题及其解决方案 ##### 2.1 可见性问题 在并发编程中,可见性问题是指当一个线程修改了共享变量的值,而另一个线程却无法及时看到这个修改的情况。这通常是因为缓存一致性协议或处理器优化导致的。Java内存模型允许编译器和运行时环境对代码进行优化,包括将变量缓存在寄存器或处理器缓存中,从而减少了与主内存之间的通信开销。然而,这种优化策略在多线程环境中可能导致数据不一致。 ##### 2.2 解决方案:volatile关键字与锁 Java提供了几种机制来解决可见性问题,其中最主要的是`volatile`关键字和锁(如`synchronized`关键字或显式锁)。 - **volatile关键字**:被`volatile`修饰的变量,其修改对所有线程立即可见。当某个线程修改了一个volatile变量的值时,Java内存模型会立即将新值同步到主内存中,并且会强制让其他线程工作内存中的该变量副本失效,从而促使它们从主内存中重新读取变量的值。这种方式确保了变量的可见性,但volatile并不保证操作的原子性。 - **锁**:通过`synchronized`关键字或`java.util.concurrent.locks`包下的锁实现(如`ReentrantLock`),可以确保在锁释放前,所有修改都同步到主内存中,并且在锁被再次获取前,其他线程无法读取到这些修改。锁不仅解决了可见性问题,还解决了原子性问题。 #### 三、有序性问题及其解决方案 ##### 3.1 有序性问题 在单线程环境中,编译器和处理器为了优化性能,可能会对代码进行重排序(Instruction Reorder)。但在多线程环境中,这种重排序可能导致程序行为难以预测,即出现有序性问题。例如,一个线程中先写后读的操作可能被重排序为先读后写,从而影响到其他线程对这些变量的观察结果。 ##### 3.2 解决方案:Happens-Before规则 Java内存模型通过定义一套“Happens-Before”规则来确保程序的有序性。这些规则规定了哪些操作必须先行发生(即这些操作不会被重排序),从而避免了由于重排序导致的并发问题。主要的Happens-Before规则包括: 1. **程序顺序规则**:一个线程中的每个操作,happens-before于该线程中的任意后续操作(即按照程序书写顺序,前面的操作先行发生)。 2. **监视器锁规则**:对一个锁的解锁,happens-before于随后对这个锁的加锁。这保证了线程在释放锁之后,对共享变量的修改对其他线程是可见的。 3. **volatile变量规则**:对一个volatile变量的写操作,happens-before于后续对这个volatile变量的读操作。这保证了volatile变量的可见性和有序性。 4. **传递性**:如果A happens-before B,且B happens-before C,那么A happens-before C。 5. **启动规则**:线程的start()方法调用,happens-before于该线程的每一个动作。 6. **终止规则**:线程的所有操作都happens-before于其他线程检测到该线程已经终止(如通过Thread.join()调用等待该线程终止)、或者该线程的终止状态被设置为true。 7. **对象终结规则**:一个对象的构造函数结束,happens-before于该对象finalizer的开始。 通过这些规则,Java内存模型为并发编程提供了一套强有力的保证,使得开发者能够编写出高效且可预测的并发程序。 #### 四、总结 Java内存模型是解决并发编程中可见性和有序性问题的关键所在。通过volatile关键字、锁机制以及Happens-Before规则,Java确保了多线程环境下对共享变量的访问既安全又高效。深入理解Java内存模型,对于编写高质量、高性能的并发程序至关重要。在并发编程的实践中,我们应充分利用这些工具和规则,同时也要注意避免过度优化和错误的使用方式,以确保程序的正确性和可维护性。
上一篇:
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
下一篇:
03 | 互斥锁(上):解决原子性问题
该分类下的相关小册推荐:
Mybatis合辑3-Mybatis动态SQL
Java语言基础7-Java中的异常
SpringBoot零基础到实战
Java语言基础1-基础知识
SpringBoot合辑-初级篇
Java语言基础4-数组详解
Java语言基础12-网络编程
Java高并发秒杀入门与实战
手把手带你学习SpringBoot-零基础到实战
Mybatis合辑2-Mybatis映射文件
Java必知必会-Maven初级
Java必知必会-JDBC