6.什么是内存屏障?具有什么作用?

什么是内存屏障?

小陈:老王,上一篇你引出了volatile底层是通过内存屏障来解决可见性和有序性问题的。首先我想问一下什么是内存屏障?

老王:内存屏障啊,本质上也是一种指令,只不过它具有屏障的作用而已。

小陈:额,这怎么说...

老王:首先内存屏障是一种指令,无论是在JAVA内存模型还是CPU层次,都是有具体的指令对应的,是一种特殊的指令。

小陈:嗯嗯,它是一种特殊的指令。还是不明白......

小陈:然后呢?

老王:然后这种指令具有屏障的作用所谓屏障,也就是类似关卡,类似栅栏,具有隔离的作用。

小陈:那它是怎么实现隔离作用的,能否搞个例子讲一下。

老王:哈哈,这个举例没问题,我先跟你说说内存屏障的分类,后面再给你实例讲一下:

老王:按照内存屏障的分类,我理解有两类。

(1)一类是强制读取主内存强制刷新主内存的内存屏障,叫做Load屏障Store屏障

(2)另外一类是禁止指令重排序内存屏障,有四个分别叫做LoadLoad屏障StoreStore屏障LoadStore屏障StoreLoad屏障

老王:下面再给你介绍一下这两类内存屏障各自的作用:

强制读取/刷新主内存的屏障

Load屏障:执行读取数据的时候,强制每次都从主内存读取最新的值。

Store屏障:每次执行修改数据的时候,强制刷新回主内存

先给你画图讲解一下Load屏障

如上图所示:在工作内存的变量名、变量的值之前有一道关卡或者栅栏,导致变量 i 获取不到工作内存中的值,所以每次只好主内存重新加载咯

然后再给讲一下Store屏障

如上图所示,每次执行assign指令将数据变更之后,后面都会紧紧跟着一个Store屏障,让你立刻刷新到主内存。

老王:小陈,我画这两图讲强制读取和刷新主内存的屏障(Load屏障Store屏障),你看懂了嘛?

小陈:也就是说,只要加了Load屏障相当于加了一个栅栏,不管工作内存是否有数据,都是从主内存读取数据。只要加了Store屏障具有强制作用,进行assign操作将变量更改了之后,立刻将变量刷新到主内存里面是吗?

老王:是的,就是这么一个道理,上面的图只是为了方便你理解画出来的,实现上并不一定完全跟图的一样,但是原理差不多。

小陈:嘿嘿,老王真棒......

禁止指令重排序的屏障

老王:好了,下面再给你讲讲另外一类的内存屏障,下面这类的内存屏障的作用是禁止指令重排序,JAVA内存模型层次关于禁止重排序有下面4种屏障:

LoadLoad屏障

序列:load1指令 LoadLoad屏障 load2指令

作用:在load1指令和load2指令之间加上 LoadLoad屏障,强制先执行load1指令再执行load2指令;load1指令和load2指令不能进行重排序(LoadLoad屏障  前面load指令禁止和屏障后面的load指令进行重排序)。

StoreStore屏障

序列:store1指令 StoreStore屏障 store2指令

作用:在store1指令和store2指令之间加上StoreStore屏障,强制先执行store1指令再执行store2指令;store1指令不能和store2指令进行重排序(StoreStore屏障  前面的store指令禁止和屏障后面的store指令进行重排序

LoadStore屏障

序列:load1指令 LoadStore屏障 store2指令

作用:在load1指令和store2指令之前加上LoadStore屏障,强制先执行load1指令再执行store2指令;load1指令和store2执行不能重排序(LoadStore屏障  前面的load执行禁止和屏障后面的store指令进行重排序

StoreLoad屏障

序列:store1指令 StoreLoad屏障 load2指令

作用:在store1指令和load2指令之间加上StoreLoad屏障,强制先执行store1指令再执行load2指令;

store1指令和load2指令执行不能重排序(StoreLoad屏障  前面的Store指令禁止和屏障后面的Store/Load指令进行重排

老王:小陈,我上面说的这几种内存屏障,你能理解吗?

小陈:感觉听着有点玄乎啊,实际上是怎么禁止重排序的啊...

老王:我下面给你画个图,以StoreStore屏障和StoreLoad屏障举个例子,你就知道大概是什么意思了: 

(1)有三个区域分别是区域1区域2区域3

(2)区域1和区域2加了 StoreStore屏障,这样区域1和区域2的Store指令被隔离开来,不能重排了

(3)区域2和区域3加了StoreLoad屏障,这样区域2和区域3的Store指令、Load指令被隔离开来,不能重排了

(3)就相当于搞了个栅栏,禁止各个区域之间的指令跳来跳去的,否则就会导致乱序执行

小陈:哎呀,原来是搞了个栅栏啊,说白了就是相当于搞了个围墙,不让各个指令之间跳来跳去的,这样达到禁止区域之间重排序的效果。

小陈:牛啊,老王;看起来这么深奥的道理,竟被你搞个图这么简单的说清楚了,献上我的膝盖...

老王:嘿嘿,还好还好......

小陈:内存屏障的底层是怎么保证可见性和有序性的,通过这次讨论我明白了但是volatile是怎么使用内存屏障的我还不明白啊?

老王:别急啊,小陈。慢慢来,今天我们先讲到内存屏障这里,volatile怎么使用内存屏障的?我们下一章再说一下。

其实不只是volatile使用到了内存屏障,还有像是synchronized关键字底层也是用到内存屏障的。

今天我们先说到这里,下一篇《volatile怎么通过内存屏障保证可见性和有序性?》我们来继续讨论

小陈:好的,老王,我们下一章见。

关注小陈,公众号上更多更全的文章

JAVA并发文章目录(公众号)

JAVA并发专题 《筑基篇》

1.什么是CPU多级缓存模型?

2.什么是JAVA内存模型?

3.线程安全之可见性、有序性、原子性是什么?

4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?

JAVA并发专题《练气篇》

5.volatile怎么保证可见性?

6.什么是内存屏障?具有什么作用?

7.volatile怎么通过内存屏障保证可见性和有序性?

8.volatile为啥不能保证原子性?

9.synchronized是个啥东西?应该怎么使用?

10.synchronized底层之monitor、对象头、Mark Word?

11.synchronized底层是怎么通过monitor进行加锁的?

12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

13.synchronized怎么保证可见性、有序性、原子性?

JAVA并发专题《结丹篇》

14. JDK底层Unsafe类是个啥东西?

15.unsafe类的CAS是怎么保证原子性的?

16.Atomic原子类体系讲解

17.AtomicInteger、AtomicBoolean的底层原理

18.AtomicReference、AtomicStampReference底层原理

19.Atomic中的LongAdder底层原理之分段锁机制

20.Atmoic系列Strimped64分段锁底层实现源码剖析

JAVA并发专题《金丹篇》

21.AQS是个啥?为啥说它是JAVA并发工具基础框架?

22.基于AQS的互斥锁底层源码深度剖析

23.基于AQS的共享锁底层源码深度剖析

24.ReentrantLock是怎么基于AQS实现独占锁的?

25.ReentrantLock的Condition机制底层源码剖析

26.CountDownLatch 门栓底层源码和实现机制深度剖析

27.CyclicBarrier 栅栏底层源码和实现机制深度剖析

28.Semaphore 信号量底层源码和实现机深度剖析

29.ReentrantReadWriteLock 读写锁怎么表示?

30. ReentrantReadWriteLock 读写锁底层源码和机制深度剖析

JAVA并发专题《元神篇》并发数据结构篇

31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?

32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?

33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?

34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?

35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?

36.DelayQueue 底层源码剖析,延时队列怎么实现?

37.SynchronousQueue底层原理解析

JAVA并发专题《飞升篇》线程池底层深度剖析

38. 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?

39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?

40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?

41. ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?

42. ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?

43. ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?

44. ThreadPoolExecutor shutdown、shutdownNow内部核心流程

45. 再回头看看为啥不推荐Executors提供几种线程池?

46. ThreadPoolExecutor线程池篇总结

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
-多个线程的几种实现方式包括:承Thread类,实现Runnable接口,实Callable接口,使用线程池。 - Java中的线程池是通过ThreadPoolExecutor类实现的。线程池维护了一个线程队列,可以复用线程,减少线程的创建和销毁开销,提高了性能。 - 不建议直接使用Executors工具类创建线程池是因为它使用的是默认的线程池配置,可能导致线程数量过多,耗尽系统资源。OOM(Out of Memory)是由于创建过多的线程导致内存不足而发生的错误。 - Java内存模型(JMM)是一种规范,定义了多线程程序中各个变量的访问方式。它包括主内存和工作内存,通过控制变量的可见性和原子性来保证线程间的通信与同步。 - 并发编程可能会发生的问题包括:竞态条件、死锁、活锁、饥饿等。可见性问题指一个线程对共享变量的修改对其他线程是否可见,原子性问题指一个操作是否可以被中断或者同时执行。 - 并发编程下会出现原子性问题是因为多个线程同时修改同一个共享变量时,可能会导致不一致的结果。有序性问题是指程序执行的顺序与预期不符。可以使用synchronized关键字、Lock锁等来解决原子性和有序性问题。加上volatile关键字可以保证可见性,禁止指令重排序。 - 内存屏障是通过编译器和处理器来实现的,用于控制指令的执行顺序和内存的可见性。synchronized关键字会在进入和退出临界区时加上内存屏障。 - 单线程指令重排在不影响单线程执行结果的前提下进行优化,但可能会影响多线程的正确性。双重校验锁中使用volatile是为了禁止指令重排,确保多线程环境下的正确性。 - InnoDB的索引是通过B+树实现的。B+树具有树高度低、查询效率高、支持范围查询等优势。 - 聚簇索引与非聚簇索引的区别在于数据的存储方式。聚簇索引将数据行存储在叶子节点中,非聚簇索引则将叶子节点指向数据行。不是所有情况都需要取回表的数据,可以通过覆盖索引来避免回表操作。 - 最左前缀匹配指在使用联合索引时,只有从左到右使用索引的前缀部分才能发挥索引的作用。将区分度高的字段放在最左边可以提高索引的效率。唯一索引与普通索引的区别在于是否允许重复值。 - 排查慢SQL可以通过查看慢查询日志、使用性能分析工具(如EXPLAIN、SHOW PROFILE)、优化查询语句等方法。 - MySQL的锁包括行锁和表锁。行锁在并发性能上更好,但需要更多的系统资源,适合处理并发访问较高的场景。表锁在资源消耗上较少,但并发性能相对较差,适合处理并发访问较低的场景。 - FOR UPDATE语句会对查询到的行加上行锁。 - 悲观锁是指在操作数据时始终假设会发生并发冲突,因此会将数据加锁以阻止其他事务的访问。乐观锁是指不加锁,而是通过版本号或时间戳等机制来判断是否发生冲突,减少了加锁的开销。悲观锁适用于并发冲突较多的场景,乐观锁适用于并发冲突较少的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值