------------------------------------------------------------------
  刚开始认识volatile的时候,觉得对它的一些特性非常迷惑。比如:具有可见性,如果一个线程修改了volatile变量的值,那么其它线程也会发现这一点;同时它又不具有原子性,多个线程对被volatile修饰的int 变量累加会造成相互覆盖。这我就迷糊了:不是一个线程修改了,其它的线程中数据都无效了么,既然会重新读取,为啥最终还会相互覆盖呢?
volatile原理:
  我们知道:如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。这个就是所谓的“可见性”,就是一个线程修改了,其他线程能知道这个操作,这就是可见性。如何实现的呢?volatile修饰的变量在生成汇编代码的时候,会产生一条lock指令,lock前缀的指令在多核处理器下会引发两件事情:
  1、将当前处理器缓存航的数据写回到系统内存;
  2、这个写回内存的操作会使得在其它cpu里缓存了该内存地址的数据无效;
  这个使得其它cpu里数据无效又是怎么实现的呢?
  cpu处理数据速度是很快的,为了提高处理速度,充分发挥cpu性能,cpu不直接跟内存进行通信,而是先将数据读入cpu高速缓存后再进行操作,但操作完不知道何时回写到内存。如果对声明了volatile的变量进行写操作,jvm就会向处理器发送一条lock前缀指令,将这个变量所在缓存行的数据写回到系统内存。但就算写回到内存,如果其它处理器缓存的还是旧值,再执行计算操作就会有问题。所以多处理器下,为了保证各个处理器的缓存是一致的,就有了一个“缓存一致性协议”,所有硬件厂商都要按照这个标准来生产硬件。具体就是每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存。
注意,如果该数据已经在别的处理器线程被修改过了,只是没有刷新到内存,则这时候是不会重新读数据的,而是等一下直接刷新到内存,这就造成了覆盖的事情发生;别的线程重新读取数据仅仅是在将变量读到了cpu缓存,还没有使用的时候才有的,一旦使用了,即使发现被修改了,也不会重新读取重新计算。具有可见性,而又多线程不安全的问题就是这样产生的。
  该部分可以结合:jvm线程模型jvm8种内存基本操作
synchronized原理:
  synchronized是用java的monitor机制来实现的,就是synchronized代码块或者方法进入及退出的时候会生成monitorenter跟monitorexit两条命令。线程执行到monitorenter时会尝试获取对象所对应的monitor所有权,即尝试获取的对象的锁;monitorexit即为释放锁。
  monitor机制是跟java对象结构相关的。HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头,实例数据跟对齐填充。

从上面的这张图里面可以看出,对象在内存中的结构主要包含以下几个部分:
  • Mark Word(标记字段):对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
  • Klass Pointer(Class对象指针):Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
  • 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节
  • 对齐:最后一部分是对齐填充的字节,按8个字节填充。
  • 其实,如果是数组对象,头信息还包括一个Array length的内容,用来记录数组长度。
  我们看这个Mark Word,它包含对象的hashcode,分代年龄跟锁标记位3部分。具体结构如下(32位虚拟机): 
  64位虚拟机下,Mark Word是64bit的,存出结果如下:

  这么复杂的结构,跟synchronized有什么关系呢?
  当然有关系,synchronized就是利用以上结构来实现的,每次就是抢占上边的Mark Word,然后修改里边各个小段的内容;然后,jdk的开发人员经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,那我们老是抢来抢去的岂不是没意义。于是考虑进行优化,也就有了偏向锁,轻量级锁以及重量级锁的概念。然后接下来我们来看这三种锁究竟是怎么一回事儿。
  偏向锁
  简单的讲,就是在锁对象的对象头中有个ThreaddId字段,这个字段如果是空的,
  第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.
  这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提高了效率。
  但是偏向锁也有一个问题,就是当锁有竞争关系的时候,需要解除偏向锁,使锁进入竞争的状态。

 
上图中只讲了偏向锁的释放,其实还涉及偏向锁的抢占,其实就是两个进程对锁的抢占,在synchrnized锁下表现为轻量锁方式进行抢占。
注:也就是说一旦偏向锁冲突,双方都会升级为轻量级锁。(这一点与轻量级->重量级锁不同,那时候失败一方直接升级,成功一方在释放时候notify)

  轻量级锁:
  之后会进入到轻量级锁阶段,两个线程进入锁竞争状态(注,我理解仍然会遵守先来后到原则;注2,的确是的,下图中提到了mark word中的lock record指向堆栈中最近的一个线程的lock record),一个具体例子可以参考synchronized锁机制。

  每一个线程在准备获取共享资源时:
  第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁” ;
  第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。
  第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord;
  第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋 第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败 第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己;
  重量级锁:
  这个就是我们平常说的synchronized锁,如果抢占不到,则线程阻塞,等待正在执行的线程结束后唤醒自己,然后重新开始竞争。之所以说是重量级,是因为线程阻塞会让出cpu资源,从内核态转换为用户态,然后执行的时候再次转换为内核态,这个过程中,cpu要切换线程,看这个线程上次执行到哪儿了,这次应该从哪儿开始,相关的变量有哪些之类的,这就是所谓的执行上下文,这个上下文的切换对宝贵的cpu资源来说是“无用功”,因为这是在为执行做准备条件,如果cpu大量的时间用在这些“无用功”上,当然也就出活儿少,也就影响执行效率了。
synchronized就是这样,默认开始是偏向锁,有竞争就逐渐升级,最终可能是重量级锁的一个过程。锁定的区域就是对象的Mark Word的内容。
  总结:为什么偏向锁跟轻量级锁相对来说速度快,就是因为这两个没有这个线程切换的过程。偏向锁是直接自己一直在用,相当于没有同步操作,轻量级锁是看了下有人在用,自己觉得他们用的时间应该不长,我就死循环等一下你们吧。大概就是这么个逻辑。当然了,死循环结束,发现你丫还没执行完,没的说,升级成重量级锁吧。

 

volatile与synchronized实现原理的更多相关文章

  1. Java并发编程知识点总结Volatile、Synchronized、Lock实现原理

    Volatile关键字及其实现原理 在多线程并发编程中,Volatile可以理解为轻量级的Synchronized,用volatile关键字声明的变量,叫做共享变量,其保证了变量的“可见性”以及“有序 ...

  2. volatile、Synchronized实现变量可见性的原理,volatile使用注意事项

    变量不可见的两个原因 Java每个线程工作都有一个工作空间,需要的变量都是从主存中加载进来的.Java内存模型如下(JMM): 线程访问一个共享的变量时,都需要先从主存中加载一个副本到自己的工作内存中 ...

  3. 从JMM透析volatile与synchronized原理,图文并茂

    在面试.并发编程.一些开源框架中总是会遇到 volatile 与 synchronized .synchronized 如何保证并发安全?volatile 语义的内存可见性指的是什么?这其中又跟 JM ...

  4. Java 并发编程:volatile的使用及其原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  5. 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲

    前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...

  6. JAVA多线程之volatile 与 synchronized 的比较

    一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空 ...

  7. synchronized实现原理

    线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案, ...

  8. ThreadLocal、Volatile、synchronized、Atomic

    前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...

  9. 多线程总结2之volatile和synchronized(转)

    本文转自 http://www.jasongj.com/java/thread_safe/ 一.多线程编程中的三个核心概念 本篇文章将从这三个问题出发,结合实例详解volatile如何保证可见性及一定 ...

随机推荐

  1. BZOJ 1977 严格次小生成树

    小C最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当小C洋洋得意之时,小P又来泼小C冷水了.小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小 ...

  2. add以及update

    const addressData = { name: this.post('name'), mobile: this.post('mobile'), province_id: this.post(' ...

  3. Maven整理笔记のMaven仓库

    Maven坐标和依赖是任何一个构件在Maven世界中的逻辑表示方式:而构件的物理表示方式是文件,Maven通过仓库来统一管理这些文件.  Maven仓库 在Maven的世界中,任何一个依赖.插件或者项 ...

  4. Linq实战 之 DataSet操作详解

    Linq实战 之 DataSet操作详解  一:linq to Ado.Net 1. linq为什么要扩展ado.net,原因在于给既有代码增加福利.FCL中在ado.net上扩展了一些方法. 简单一 ...

  5. Win8共享wifi热点设置

    Win8共享wifi热点如何设置?大家都知道win7系统可以实现wifi热点共享,那么win8应该也能实现wifi热点共享,那么如何设置win8不需要任何软件只需要对电脑进行设置就可以共享无线上网. ...

  6. CentOS 傻瓜式部署uWSGI + nginx + flask

    交代背景 这篇帖子是为了提供我自己的July Novel站点的小说数据支撑.解决分布式部署爬虫程序的繁琐过程,由于本人对shell编程并不熟悉,故而先逐步记录操作步骤,通过以下操作达到节省时间的方式. ...

  7. C#面向对象的三大基本特征

    封装: 封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现(可以使代码更加安全),外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现,正是由于封装机制,程 ...

  8. vue.js 知识点(二)

    关于vue看到有很多的知识点和react有很多相近的地方,比如说路由还有一些简单的运用,但是又有一些不同,比如格式.还有写法的一些不同! 所以在这里我总结一下关于vue 关于路由的一些运用: 路由: ...

  9. CVE-2012-2122-Mysql身份认证漏洞及利用

    一.漏洞简介 当连接MariaDB/MySQL时,输入的密码会与期望的正确密码比较,由于不正确的处理,会导致即便是memcmp()返回一个非零值,也会使MySQL认为两个密码是相同的.按照公告说法大约 ...

  10. logstash--使用ngxlog收集windows日志

    收集流程 1nxlog => 2logstash => 3elasticsearch 1. nxlog 使用模块 im_file 收集日志文件,开启位置记录功能 2. nxlog 使用模块 ...