此次文章主要探讨volatile与synchronized,通过一些基础概念的介绍,让读者对于两者有更深的了解。

一、几个相关概念

1、原子性

  其本意是“不能被进一步分隔的最小粒子”,而原子操作意为“不可被中断的一个或一系列操作”。在多处理器重实现原子操作变得有点复杂。

1)操作系统如何实现原子性。

  单处理器可以对同一个缓存行里自动进行16/32/64位的原子操作。但是复杂的内存操作处理器是不能保证其原子性的,比如跨总线宽度、跨多个缓存行和跨页表的访问。例如,i++是一个读改写的操作,由于该代码可能被不同的线程执行导致最终出现的结果可能不是我们想要的结果(具体原因不在此赘述)。但是,处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。

a。使用总线锁保证原子性

  处理器通过使用总线锁来解决i++问题。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,此时该处理器可以独占共享内存。

b。使用缓存锁来保证原子性

  总线锁把CPU与内存间的通信锁住了,这使得在锁定期间,其它处理器不能操作其它内存地址的数据,所以总线锁开销比较大。我们只需要保证对某个内存地址的操作是原子性即可(减小锁粒度)。目前处理器在某些场合下回使用缓存锁定来代替总线锁来进行优化。

2、可见性

  可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。

3、指令重排

  重排序指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。在多线程的程序中,对某些指令的重排序可能会改变程序的执行结果(后续会有实例说明)。

二、volatile

1、volatile变量具有以下特性。

  可见性:对一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入、

  禁止重排序:jdk1.5以后对volatile语义进行了加强,不允许volatile变量之间进行重排序。

2、底层实现原理

1)操作系统层面

  操作系统可通过LOCK#前缀指令实现以上前两个特性。为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1、L2或其它)后再进行操作,但操作完不知道何时写回到内存(操作系统这里其实使用了异步操作来解决生产消费速度不均的问题)。如果申明了volatile的变量进行写操作,JVM就会想处理器发送一条Lock前缀指令,将这个变量的缓存行的数据写回到内存中。此时,其它处理器中的值还是旧值。在多处理器下,为了保证各个处理器缓存一致,实现了缓存一致性协议。每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行过期时,就会将当前处理器的缓存行置为无效状态,当处理器对这个数据进行修改时,会重新从操作系统内存中把数据读取到处理器缓存中。

  a。Lock前缀指令会引起处理器缓存回写到内存。

  Lock前缀指令导致在执行指令期间,声言处理器的Lock信号。在多处理器环境下,处理器可以独占任何共享内存。操作系统通过总线锁或者缓存锁定,来确保同时只能有一个处理器可修改缓存数据。

  b。一个处理器的缓存会写到内存导致其它处理器的缓存无效。

2)JMM层面。

  在Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。局部变量,方法定义参数和异常处理参数不会在线程之间共享,它们不会有内存可见性问题。

  从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了改线程共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。JMM的抽象示意图如下所示。

  当一个变量被申明为volatile时。

  写入操作:JMM会把该线程对应的本地内存中的变量刷新到主存中。

  读取操作:JMM会把本地内存置为无效,线程接下来会从主存中读取共享变量。

        

3)禁止重排序应用

  在单例模式中,人们使用了双重校验来降低锁同步的开销,查看以下无volatile时的代码。

  

  以上是一个错误的优化,当线程执行到第4行时,代码读取到instance不为null,但是注意此时instance引用的对象还未初始化。原因如下。

  instance = new Singleton()可以分解为如下3行伪代码。

    memory = allocate();//1.分配对象的内存空间。

    ctorInstance(memory);//2.初始化对象

    instance = memory;// 3.设置instance指向刚刚分配的地址

  步骤2和3由于指令重排,可能导致另一个线程访问到未被初始化的对象。如果在instance变量前加上volatile即可解决此问题。

 三、synchronized

1、简单介绍

  synchronized简单的理解就是对象锁,Java中的每一个对象都可以作为锁。它主要可以确保代码一系列操作在同一线程只能由一个线程访问。

具体表现为一下3种形式。

  对于普通同步方法,锁是当前实例对象。

  对于静态同步方法,锁是当前的Class对象。

  对于同步方法块,锁是synchronized括号里配置的对象。

2、原理介绍。

  在Java中任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入到同步块或者同步方法中,而没有获取到监视器的线程将会被阻塞在同步块或者同步方法入口处,进入BLOCKED状态。当访问访问Object的前驱(获得了锁的线程)释放了锁,则会唤醒阻塞在同步队列中的线程,使其重新尝试对监视器获取。具体过程如下图。

         

四、synchronized和volatile比较

1、原理分析

  从原理上分析,volatile是JMM借助操作系统底层指令实现的关键字。当变量被它修饰时,它可以保证对于该变量的访问都需要从共享内存中获取,而每次修改则必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。synchronized是JVM层面的,它借助Java对象的monitor实现的,同一时刻只能有一个线程进入监视器,它可以保证程序对于变量访问的可见性和排它性。

  为了实现线程间的同步,两者都是通过总线锁/内存锁定、monitor这样的方式,即线程在同一时刻只能访问对应的内存区域,这样就避免了多个线程同时写入内存导致结果无法预知的情况。

2、特性对比

1)volatile

  a.可见性:可保证变量在内存中的可以性。

  b.多数情况下相对synchronized具有较高的性能

  c.有序性:在某些情况下可以防止指令重排(经典案例为单例双重校验)

2)synchronized

  a.性能相对较差,但是1.6以后性能有所提升。

  b.有序性,被加锁的代码块同一时刻只能有一个线程访问程序,保证程序有序执行

五、总结思考

1)为了解决多线程间的同步问题核心思想:通过限定同一时刻仅有一个线程有写入。

2)操作系统通过减小锁的粒度提升性能。

转载于:https://www.cnblogs.com/han02216/p/8510501.html

并发编程学习(2)----volatile与synchronized相关推荐

  1. Java并发编程学习笔记——volatile与synchronized关键字原理及使用

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. 一.vo ...

  2. Java 并发编程CAS、volatile、synchronized原理详解

    CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...

  3. libevent c++高并发网络编程_高并发编程学习(2)——线程通信详解

    前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...

  4. 高并发编程学习(2)——线程通信详解

    前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...

  5. 并发编程系列之volatile关键字详解

    并发编程系列之volatile关键字详解 1.volatile是什么? 首先简单说一下,volatile是什么?volatile是Java中的一个关键字,也是一种同步机制.volatile为了保证变量 ...

  6. java并发编程学习一

    java并发编程学习一 什么是进程和线程? 进程是操作系统进行资源分配的最小单位 进程跟进程之间的资源是隔离的,同一个进程之中的线程可以共享进程的资源. 线程是进程的一个实体,是CPU 调度和分派的基 ...

  7. java中解决脏读_java并发编程学习之脏读代码示例及处理

    使用interrupt()中断线程     当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即 ...

  8. java volatile 原子性_Java并发编程之验证volatile不能保证原子性

    Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...

  9. Java高并发编程学习(三)java.util.concurrent包

    简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...

  10. java公平锁和非公平锁_java并发编程学习之再谈公平锁和非公平锁

    在java并发编程学习之显示锁Lock里有提过公平锁和非公平锁,我们知道他的使用方式,以及非公平锁的性能较高,在AQS源码分析的基础上,我们看看NonfairSync和FairSync的区别在什么地方 ...

最新文章

  1. 数据蒋堂 | 内置的数据无法实现高性能
  2. ajax发送post请求_按键精灵安卓版发送post和get请求
  3. xss防御方法base64_XSS 防御方法总结
  4. 重磅!阿里云MongoDB 5.0发布,速来围观新特性
  5. .NET Core中的CSV解析库
  6. Drools和jBPM KIE A​​pps平台
  7. mysql binlog空间维护
  8. 互联网晚报 | 1月15日 星期六 | 娃哈哈董事长称准备6亿发年终奖;河南省消协对辛巴提起公益诉讼;支付宝上线消息“刷子”功能...
  9. php考过来运行后报乱码,PHP接收GET中文参数乱码怎么办
  10. win764位和32位有什么区别_32位、64位它们是什么关系?它们又有什么区别?
  11. XJOI 3281 A * B Problem again 题解
  12. 软工导论 12-13-2 实验任务一
  13. python3--命名空间字典
  14. 心电图分析软件_狼疮性心肌炎39例临床特点及预后分析
  15. UniWebView for Unity移动端浏览器插件的一些基本情况
  16. 51单片机流水灯方法大全
  17. composer 进行tp6的安装
  18. YOLOv4论文(中文版)
  19. PrintStream和System.setOut(PrintStream ps)用法
  20. 【C语言编程--水仙花数II】

热门文章

  1. CSDN APP又出错了,看不到博文
  2. 由大脑工作原理,探讨向菩萨求聪明的灵验的科学原理
  3. 问题解决办法:pip tensorrt成功,PyCharm import出错
  4. 听说杭州湾大桥因为影响候鸟迁徙被抗议
  5. 二值化_二值化算法之宇智波鼬
  6. ajax传单参数接受不了,Choropleth传单ajax
  7. linux查找当前目录下所有子目录特定文件类型
  8. Arduino 函数-IO输出输入
  9. ajax请求进error怎么弹出错诶信息,在ajax请求jqgrid之后出现错误时显示错误消息...
  10. 为什么root下不能使用passwd命令_Linux:CentOS 7中常用的基础命令