60 主内存和工作内存的关系?
本课时我们主要讲解主内存和工作内存的关系。 CPU 有多级缓存,导致读的数据过期
由于 CPU 的处理速度很快,相比之下,内存的速度就显得很慢,所以为了提高 CPU 的整体运行效率,减少空闲时间,在 CPU 和内存之间会有 cache 层,也就是缓存层的存在。虽然缓存的容量比内存小,但是缓存的速度却比内存的速度要快得多,其中 L1 缓存的速度仅次于寄存器的速度。结构示意图如下所示:
在图中,从下往上分别是内存,L3 缓存、L2 缓存、L1 缓存,寄存器,然后最上层是 CPU 的 4个核心。从内存,到 L3 缓存,再到 L2 和 L1 缓存,它们距离 CPU 的核心越来越近了,越靠近核心,其容量就越小,但是速度也越快。正是由于缓存层的存在,才让我们的 CPU 能发挥出更好的性能。
其实,线程间对于共享变量的可见性问题,并不是直接由多核引起的,而是由我们刚才讲到的这些 L3 缓存、L2 缓存、L1 缓存,也就是多级缓存引起的:每个核心在获取数据时,都会将数据从内存一层层往上读取,同样,后续对于数据的修改也是先写入到自己的 L1 缓存中,然后等待时机再逐层往下同步,直到最终刷回内存。
假设 core 1 修改了变量 a 的值,并写入到了 core 1 的 L1 缓存里,但是还没来得及继续往下同步,由于 core 1 有它自己的的 L1 缓存,core 4 是无法直接读取 core 1 的 L1 缓存的值的,那么此时对于 core 4 而言,变量 a 的值就不是 core 1 修改后的最新的值,core 4 读取到的值可能是一个过期的值,从而引起多线程时可见性问题的发生。 JMM的抽象:主内存和工作内存 什么是主内存和工作内存 Java 作为高级语言,屏蔽了 L1 缓存、L2 缓存、L3 缓存,也就是多层缓存的这些底层细节,用 JMM 定义了一套读写数据的规范。我们不再需要关心 L1 缓存、L2 缓存、L3 缓存等多层缓存的问题,我们只需要关心 JMM 抽象出来的主内存和工作内存的概念。为了更方便你去理解,可参考下图:
每个线程只能够直接接触到工作内存,无法直接操作主内存,而工作内存中所保存的正是主内存的共享变量的副本,主内存和工作内存之间的通信是由 JMM 控制的。 主内存和工作内存的关系 JMM 有以下规定:
(1)所有的变量都存储在主内存中,同时每个线程拥有自己独立的工作内存,而工作内存中的变量的内容是主内存中该变量的拷贝;
(2)线程不能直接读 / 写主内存中的变量,但可以操作自己工作内存中的变量,然后再同步到主内存中,这样,其他线程就可以看到本次修改;
(3) 主内存是由多个线程所共享的,但线程间不共享各自的工作内存,如果线程间需要通信,则必须借助主内存中转来完成。
听到这里,你对上图的理解可能会更深刻一些,从图中可以看出,每个工作内存中的变量都是对主内存变量的一个拷贝,相当于是一个副本。而且图中没有一条线是可以直接连接各个工作内存的,因为工作内存之间的通信,都需要通过主内存来中转。
正是由于所有的共享变量都存在于主内存中,每个线程有自己的工作内存,其中存储的是变量的副本,所以这个副本就有可能是过期的,我们来举个例子:如果一个变量 x 被线程 A 修改了,只要还没同步到主内存中,线程 B 就看不到,所以此时线程 B 读取到的 x 值就是一个过期的值,这就导致了可见性问题。
以上就是本课时的内容了,本课时主要介绍了 CPU 的多层缓存结构,以及由此抽象出来的 JMM 主内存和工作内存的结构图,并且还介绍了主内存和工作内存之间的关系。听完本课时,你会更加深刻的理解为什么会发生可见性问题。
文章列表
- Java并发编程-00由点及面,搭建你的Java并发知识网
- Java并发编程-01为何说只有1种实现线程的方法?
- Java并发编程-02如何正确停止线程?为什么volatile标记位的停止方法是错误的?
- Java并发编程-03线程是如何在6种状态之间转换的?
- Java并发编程-04waitnotifynotifyAll方法的使用注意事项?
- Java并发编程-05有哪几种实现生产者消费者模式的方法?
- Java并发编程-06一共有哪3类线程安全问题?
- Java并发编程-07哪些场景需要额外注意线程安全问题?
- Java并发编程-08为什么多线程会带来性能问题?
- Java并发编程-09使用线程池比手动创建线程好在哪里?
- Java并发编程-10线程池的各个参数的含义?
- Java并发编程-11线程池有哪4种拒绝策略?
- Java并发编程-12有哪6种常见的线程池?什么是Java8的ForkJoinPool?
- Java并发编程-14为什么不应该自动创建线程池?
- Java并发编程-15合适的线程数量是多少?CPU核心数和线程数的关系?
- Java并发编程-16如何根据实际需要,定制自己的线程池?
- Java并发编程-17如何正确关闭线程池?shutdown和shutdownNow的区别?
- Java并发编程-19你知道哪几种锁?分别有什么特点?
- Java并发编程-21如何看到synchronized背后的“monitor锁”?
- Java并发编程-23Lock有哪几个常用方法?分别有什么用?
- Java并发编程-25读写锁ReadWriteLock获取锁有哪些规则?
- Java并发编程-26读锁应该插队吗?什么是读写锁的升降级?
- Java并发编程-27什么是自旋锁?自旋的好处和后果是什么呢?
- Java并发编程-28JVM对锁进行了哪些优化?
- Java并发编程-29HashMap为什么是线程不安全的?
- Java并发编程-30ConcurrentHashMap在Java7和8有何不同?
- Java并发编程-31为什么Map桶中超过8个才转为红黑树?
- Java并发编程-32同样是线程安全,ConcurrentHashMap和Hashtable的区别
- Java并发编程-33CopyOnWriteArrayList有什么特点?
- Java并发编程-34什么是阻塞队列?
- Java并发编程-35阻塞队列包含哪些常用的方法?add、offer、put等方法的区别?
- Java并发编程-36有哪几种常见的阻塞队列?
- Java并发编程-37阻塞和非阻塞队列的并发安全原理是什么?
- Java并发编程-38如何选择适合自己的阻塞队列?
- Java并发编程-39原子类是如何利用CAS保证线程安全的?
- Java并发编程-40AtomicInteger在高并发下性能不好,如何解决?为什么?
- Java并发编程-41原子类和volatile有什么异同?
- Java并发编程-42AtomicInteger和synchronized的异同点?
- Java并发编程-45ThreadLocal是用来解决共享资源的多线程访问的问题吗?
- Java并发编程-46多个ThreadLocal在Thread中的threadlocals里是怎么存储的?
- Java并发编程-47内存泄漏——为何每次用完ThreadLocal都要调用remove()?
- Java并发编程-48Callable和Runnable的不同?
- Java并发编程-49Future的主要功能是什么?
- Java并发编程-50使用Future有哪些注意点?Future产生新的线程了吗?
- Java并发编程-51如何利用CompletableFuture实现“旅游平台”问题?
- Java并发编程-52信号量能被FixedThreadPool替代吗?
- Java并发编程-53CountDownLatch是如何安排线程执行顺序的?
- Java并发编程-54CyclicBarrier和CountdownLatch有什么异同?
- Java并发编程-55Condition、object.wait()和notify()的关系?
- Java并发编程-56讲一讲什么是Java内存模型?
- Java并发编程-57什么是指令重排序?为什么要重排序?
- Java并发编程-58Java中的原子操作有哪些注意事项?
- Java并发编程-59什么是“内存可见性”问题?
- Java并发编程-60主内存和工作内存的关系?
- Java并发编程-61什么是happens
- Java并发编程-62volatile的作用是什么?与synchronized有什么异同?
- Java并发编程-63单例模式的双重检查锁模式为什么必须加volatile?
- Java并发编程-64你知道什么是CAS吗?
- Java并发编程-65CAS和乐观锁的关系,什么时候会用到CAS?
- Java并发编程-66CAS有什么缺点?
- Java并发编程-67如何写一个必然死锁的例子?
- Java并发编程-68发生死锁必须满足哪4个条件?
- Java并发编程-69如何用命令行和代码定位死锁?
- Java并发编程-70有哪些解决死锁问题的策略?
- Java并发编程-71讲一讲经典的哲学家就餐问题
- Java并发编程-72final的三种用法是什么?
- Java并发编程-73为什么加了final却依然无法拥有“不变性”?
- Java并发编程-75为什么需要AQS?AQS的作用和重要性是什么?
- Java并发编程-76AQS的内部原理是什么样的?
- Java并发编程-77AQS在CountDownLatch等类中的应用原理是什么?
- Java并发编程-78一份独家的Java并发工具图谱