《深入理解Java虚拟机》读书笔记--第十三章 线程安全与锁优化

                                              ------------------------每个关节都是武器,浑身上下都是才艺

目录

1.前瞻知识:

1.对象的内存布局与对象头

 3.缓存一致性协议(MESI)

 4.硬件内存模型与编程语言内存模型

《深入理解Java虚拟机》读书笔记--第十二章 Java内存模型与线程_时空恋旅人的博客-CSDN博客 

2.线程安全

         2.1线程安全的实现方法:

2.2为什么说synchronized是一个重量级的操作?

 2.3 RenntrantLock与synchronized的区别?

3.锁优化

   3.1 java 1.6以后是如何优化锁的?

   3.2 monitor对象:

  3.3 synchronized是如何优化的?锁的这四种状态是如何变化的?


1.前瞻知识:

1.对象的内存布局与对象头

 1.对象的内存布局

2.MarkWord里面内容

 3.缓存一致性协议(MESI)

    计算机世界的快速发展离不开 CPU、内存和 I/O 设备的高速发展,但是这三者一直存在速度差异性问题,我们可以从存储器的层次结构可以看出 ,  随着摩尔定律的失效,人们需要开发出更高效的软件,他的核心思想是拆分计算机处理的任务发布到多个廉价的CPU上同时执行,这样就能底层本的提高软件执行的效率。当多个任务想要进行读写同一块内存值的时候,难题就出现了。

                   

  单核处理器的缓存结构

  L1大概几十K,L2大概几百K

多核处理器的缓存: 

    

 缓存一致性协议作用的地方:

                      

  缓存的最小存储的单元称为缓存行

  

CPU中每个缓存行使用四种状态进行标记
M:Modified 修改
指的是该缓存行只被缓存在该CPU缓存中,并且是被修改过的,因此他与主存的数据是不一致的,该缓存行中的数据需要在未来的某个时间点(允许其他CPU读取主存相应的内容之前)写回主存,然后状态变成E(独享)。

   

E :Exclusive 独享
缓存行只被缓存在该CPU的缓存中,是未被修改过的,与主存的数据是一致的,可以在任何时刻当有其他CPU读取该内存时,变成S(共享)状态,当CPU修改缓存行的内容时,变成M(修改)的状态。

     

S :Share 共享
意味着该缓存行可能被多个CPU进行缓存,并且该缓存中的数据与主存数据是一致的,当有一个CPU修改该缓存行时,其他CPU是可以被作废的,变成I(无效的)。

   

I :Invalid 无效的

代表这个缓存是无效的,可能是有其他CPU修改了该缓存行。一个无效(Invalid)的缓存行必须从主存中读取(变成Share或者Exclusive状态)来满足该CPU的读请求。

现在我们来模拟一个两核处理器,他的读写操作是怎样的

      假设CPUa去读取一个数据x,那么首先他会检查自己的缓存中是否有这个数据,如果有缓存中那么就要去判断一下他的状态是什么样子的。如果是E,S,M这三个状态,那么都表示他是整个处理器中处于最新的一个值,直接读取就OK了。假设x的状态是I,那么CPUa就要通过总线通知发送一个读的请求,如果其他CPU收到了这个请求并且在缓存中的这个x的状态是E或者S,那么通过总线通知A去获取一下就行了。然后B中x的状态改成S,如果是B中的X是M态,那么B要先把X写到主存中去,然后再分享给A。

     如果CPUa要去写这个数据,那么如果x在缓存中是M或者E,那么这个表示CPU对x是独占的,直接去写就完事儿了。如果x的状态是S,说明其他CPU也有这个变量的拷贝。那么就需要通过总线通知其他的CPUj将自己缓存中的值改为I,如果说状态是I,那么就需要从主内存中去读取,然后改为M,并且通过总线通知其他的CPU都改为I。

 4.硬件内存模型与编程语言内存模型

《深入理解Java虚拟机》读书笔记--第十二章 Java内存模型与线程_时空恋旅人的博客-CSDN博客

2.线程安全

   2.1线程安全的实现方法:

   两个方面,一个是代码编写如何实现线程安全?虚拟机如何实现同步与锁

   互斥同步(临界区,互斥量,信号量都是主要的互斥方式) 这是一种悲观的并发策略,互斥是方法同步是目的,  在Java中最基本的互斥同步手段就是synchronized关键字。

  非阻塞同步 基于冲突检测的一种乐观的并发策略,例如CAS(比较并交换)就是一种乐观并发策略,说他是乐观锁的实现其实不准确,因为他是无锁编程,没有涉及到锁。

  

2.2为什么说synchronized是一个重量级的操作?

     JVM 的实现原理里面,是基于进入和退出monitor对象来实现方法的同步和代码块的同步,但是两者实现的细节不一样,代码块的同步是通过monitorenter和monitorexit指令来实现的,再往底层的指令就是lock 跟 unlock ,而方法同步是使用另外的一种方式实现。

     涉及到用户态和内核态的切换,在win和linux中,Java的原生线程与操作系统的线程是1:1的线程模型,当阻塞一个线程时,就会从用户态切换到内核态。

   synchronized的原子性是通过加锁,那么他又是怎么保证可见性的呢?进入synchronized块的内存语义就是把在synchronized块中使用的到的变量从线程工作内存中清除,这样他再次拿变量的时候就会从主存中拿了。

 2.3 RenntrantLock与synchronized的区别?

   不仅仅功能上RenntrantLock比synchronized更强大,吞吐量RenntrantLock要比synchronized高,即便是在synchronized被优化过后。

2.4 什么是CAS算法?

    CAS指令需要三个操作数,分别是内存位置V,旧的预期值A, 新的预期值B,

     当线程要修改资源对象的值的时候,他会先读取当前资源对象的值存在A中,此时该线程如果得到时间片他就继续修改资源对象的值,修改之前会先根据资源对象的内存地址V找到资源对象的值,然后做一个对比,如果资源对象的值等于旧值A,那么将其更新为新值B。另外一个线程比较失败后会进入一个自旋的状态。

    

      

        CAS为什么要原子性?如何保证CAS的原子性?

当然CAS比较交换的这个动作需要同步的才行,那怎么保证同步呢?加锁吗?当然不是CAS是无锁编程,加锁不是转着转着给转回去了吗?我们来看看,以AtomicInteger为例,他的CAS方法是调用的Unsafe类的方法,而这些方法都是native的,说明他依赖操作系统,而如今的CPU指令中恰好有CAS的指令,具有原子性。

 

2.5什么是ABA问题?

   如果一个变量在初次读取的时候就是A值,并且在准备赋值的时候检查他仍然是A值,它可能在这段时间的值被改成了B,然后又改成了A。

  如何解决ABA问题呢? 在JDK中提供了AtomicStampedReference类给每个变量的状态值都配备了时间戳,从而避免了ABA问题的产生。

2.6  Unsafe类

    Unsafe类提供了硬件级别的原子操作,方法都是native的,我们直接使用Unsafe类是不行的,因为类加载器不一样,硬要使用的话可以利用反射来使用。

  

3.锁优化

   3.1 java 1.6以后是如何优化锁的?

   锁优化都是为了解决线程之间更高效的共享数据以及解决竞争问题

   锁也分不同状态,JDK6之前只有两个状态:无锁、有锁(重量级锁),而在JDK6之后对synchronized进行了优化,新增了两种状态,总共就是四个状态:无锁状态、偏向锁、轻量级锁、重量级锁,其中无锁就是一种状态了。锁的类型和状态在对象头Mark Word中都有记录,在申请锁、锁升级等过程中JVM都需要读取对象的Mark Word数据。

    每一个锁都对应一个monitor对象,在HotSpot虚拟机中它是由ObjectMonitor实现的(C++实现)。每个对象都存在着一个monitor与之关联,对象与其monitor之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它便处于锁定状态。

    3.2 monitor对象:

   monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因

   ObjectMonitor中有两个队列_WaitSet和_EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入_EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

       

  

3.3 synchronized是如何优化的?锁的这四种状态是如何变化的?

   无锁:

     首先资源不存在竞争是无锁的,或者以非锁的方式竞争(CAS),这个时候是无锁的状态。

   偏向锁:

     无锁并不能替代有锁的情况,偏向锁的意思就是锁会偏向于第一个获得他的线程,如在在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永不再进行同步,偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不用做了。

     偏向锁的实现:先看对象的对象头的mark word里面的锁标志位是不是01,如果是,则看看是否有偏向,如有偏向,再去读mark word的前23个bit  这就是偏向的线程ID  

  轻量级锁

    当有另外的线程想要获取这个锁的时候,偏向锁的模式宣告结束,会升级成为轻量级锁,

 当锁为轻量级锁的时候,线程对象是如何与锁绑定的呢?当一个线程看到锁标志位为00的时候,就知道他是一个轻量级的锁,这时线程会在自己的虚拟机栈中建立一个Lock Record的空间,用于存放对象头部mark word的拷贝,线程使用CAS去尝试获取锁,一旦获取成功,则就会复制mark word的内容去Lock Record。并且将Lock Record中的owner指针指向该对象。对象mark word中前三十个bit将会生成一个指针指向线程虚拟栈中的Lock Record.

 这个时候这个线程已经获取了这个对象的锁,如果此时另外的线程想要获取这个锁,这个时候他就会进入一个忙循环,也就是自旋等待的时候。自旋锁不会放弃处理器的执行时间

   重量级锁

   如果在自旋状态的线程超过一个,那么锁就会膨胀成重量级锁,那么就需要使用Monitor来对线程进行控制。此时将会完全锁定资源,对线程的管控也最为严格。

    

   

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空恋旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值