volatile:

定义:Java编程语言允许线程访问共享变量,为了确保共享变量内被准确和一致性地更新,线程应该确保通过排它锁单独获得这个变量。根据volatile的定义,volatile有锁的语义。

作用:1.保证共享变量的可见性(这是volatile作为轻量级锁的基础);

    这里可见性的意思是:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值(与上篇定义的可见性有点区别啊,这里与上篇相比没有强调因重排序带来的有序性问题,进而导致的操作间可见性问题,也就是后面操作本来应该能看到前面操作结果的,结果因为重排序而看不到了,导致出现不可见性问题,笔者认为这本质上还是属于重排序问题。所以上篇定义的可见性,最后一句存在错误,但为了强调可见性与重排序(有序性)之间的区别,笔者并没有修改,当然也保留了这部分的解释)。

   2.禁止重排序(根据happens-before规则)

实现原理:

 1.保证共享变量可见性的实现原理(以X86处理器来分析):

  instance = new Single();//instance是volatile变量

  转换成汇编代码,如下:

  0x01a3deld: movb $0x0,0x1104800(%esi);0x01aa3de24:lock addl $0x0,(%esp);

  有volatile修饰的共享变量进行写操作的时候会多出第二行(带lock前缀的)汇编代码。

  Lock前缀的指令在多核处理器下的作用:

  1.将当前处理器的缓冲行的数据写会到系统内存;

  Lock前缀指令导致在执行指令期间,声言处理器的LOCK#信号。在多处理环境中,LOCL#信号确保会在声言该信号期间,处理器可以独占任何共享内存。(采取机制有锁总线,锁缓存,目前的处理大多选择后者,因为锁总线的开销太大)。  

  对于目前的处理器,如果访问的区域已经缓存在处理器内部,则不会声言LOCK#信号,处理器会锁定这块内存的缓存并刷新到内存,并使用缓存一致性机制来确保修改的原子性,此操作称为“缓存锁定”,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。同时,Lock前缀指令会引起处理器缓存刷新到内存。

  2.这个写内存的操作会使在其它CPU里缓存了该内存地址的数据无效。

  实现机制:一个处理器的缓存刷新到内存会导致其它处理器的缓存无效。处理器使用嗅探技术来保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。

  缓存一致性协议:在多处理下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址(中的数据)被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行操作的时候,会重新从系统内存把数据读到处理器缓存里(看到这里是不是明白了只对volatile变量的写操作进行了处理(增加lock前缀),因为处理器缓存里的数据有么有效,有效就是最新值,要么无效,需要到主内存去读取)。

现在总结一下LOCK前缀指令的作用:

1.确保处理器修改共享数据的原子性(此时修改的值还在缓存),通过锁总线,锁缓存实现的

2.将修改后的值刷新到内存,同时刷新内存的这个动态,会被其他处理器嗅探到,然后其他处理器会将他们缓存里的数据置为无效状态。

2,volatile禁止重排序的实现原理

在讲解实现原理之前,我们先说一下volatile的内存语义:

内存语义的实现也需要可见性作为基础的。内存屏障只是禁止重排序的基础。

volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值(所有的共享变量)刷新到主内存。

volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效(本地内存中的所有数据都无效了),线程接下来将从主内存中读取数据。

volatile内存语义的实现:

volatitle重排序规则表:

从上表可知:

1、当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。

2、当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。

3、当第一个操作是volatile写时,第二个volatile操作是volatile读时,不能重排序。

通过volatile重排序的规则,编译器编译时会在这三种情况下插入相应的内存屏障,来保证volatile的内存语义。插入内存屏障的规则:

1、在每volatile写,之前插入StoreStore内存屏障指令,之后插入StoreLoad屏障;

2、在每volatile读,之后插入内存LoadLoad,LoadStore屏障指令。

这些内存屏障插入措施是非常保守的。因为有些内存屏障可以根据内存访问的具体情况(比如只有一个volatile读时,所有的屏障都可以省略)是可以省略掉的。尤其是这些措施是独立于处理器平台之上的,不同的处理器重排序的规则都不一样。比如,X86处理器,只允许写/读重排序,这种情况下,上面的内存屏障插入规则只需要StoreLoad就可以了。

根据volatile禁止重排序的规则,以及内存屏障指令的作用,我们可以总结volatile的一个特性:

对volatile变量的单个读/写,可以看成是使用同一个锁对这些单个volatile读/写做了同步,正是如此,volatile被称为轻量级锁。在功能上,锁比volatile更强大;在可伸缩性和执行性能上,volatile更有优势。

不同的处理器进行重排序的规则也不相同,以X86处理器为例,它允许的重排序就只有写-读。

synchronized

final

Java并发之同步原语的更多相关文章

  1. java并发之同步辅助类CyclicBarrier和CountDownLatch

    CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门, ...

  2. Java并发之同步工具类

    1. CountDownlatch(计数器) 描述: 一个同步工具类,允许一个或多个线程等待其它线程完成操作 类图 通过指定的count值进行初始化,调用await方法的线程将被阻塞,直到count值 ...

  3. java并发之同步辅助类CountDownLatch

    CountDownLatch 含义: CountDownLatch可以理解为一个计数器在初始化时设置初始值,当一个线程需要等待某些操作先完成时,需要调用await()方法.这个方法让线程进入休眠状态直 ...

  4. java并发之同步辅助类(Semphore、CountDownLatch、CyclicBarrier、Phaser)

    线程同步辅助类,主要学习两点: 1.上述几种同步辅助类的作用以及常用的方法 2.适用场景,如果有适当的场景可以用到,那无疑是最好的 semaphore(seməˌfôr) 含义 信号量就是可以声明多把 ...

  5. JAVA并发之锁获取步骤及锁优化

    在另外的两篇文章中先后介绍了轻量级同步关键字volatile和重量级锁关键字synchronized,这两个关键字是Java语言中进行线程同步的基本方式(当然还有ReentrenLock等显式锁方式) ...

  6. 从同步原语看非阻塞同步以及Java中的应用

    非阻塞同步:基于冲突检测的乐观并发策略,通俗讲就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果争用数据有冲突那就采用其他的补偿措施(最常见的就是不断重试直到成功),这种乐观的并发策略 ...

  7. java并发之线程同步(synchronized和锁机制)

    使用synchronized实现同步方法 使用非依赖属性实现同步 在同步块中使用条件(wait(),notify(),notifyAll()) 使用锁实现同步 使用读写锁实现同步数据访问 修改锁的公平 ...

  8. java并发之固定对象与实例

    java并发之固定对象与实例 Immutable Objects An object is considered immutable if its state cannot change after ...

  9. 深入理解Java并发之synchronized实现原理

    深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...

随机推荐

  1. Centos6设置DNS

    通过编辑 vi /etc/resolv.conf 设置首选DNS和次要DNS.如下,排在前面的就是首选DNS,后面一行就是次要的DNS服务器DNS vi /etc/resolv.conf namese ...

  2. 20172330 2017-2018-1 《Java程序设计》第九周学习总结

    20172330 2017-2018-1 <程序设计与数据结构>第九周学习总结 教材学习内容总结 本周的学习包括两章内容,分别为异常和递归. 异常 错误和异常都是对象,代表非正常情况或者无 ...

  3. Spark Streaming - DStream

    1 Overview Spark Streaming is an extension of the core Spark API that enables scalable, high-through ...

  4. POJ 2823 (滑动窗口)

    这道题最容易想到的是用朴素的做法,即 每滑动一次,就遍历一次窗口找出最大最小值,这样时间复杂度为O(n*k),由于题目数据比较大,这种做法肯定是超时的. 另外,根据书上的讲解,还可以采用优先队列来求解 ...

  5. HDU 5816 Hearthstone 概率dp

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5816 Hearthstone Time Limit: 2000/1000 MS (Java/Othe ...

  6. noauth authentication required redis

    解决方案: 这是出现了认证的问题,是因为设置了认证密码. 127.0.0.1:6379> auth "yourpassword" 例如:

  7. Python的time,datetime,string相互转换

    #把datetime转成字符串 def datetime_toString(dt): return dt.strftime("%Y-%m-%d-%H") #把字符串转成dateti ...

  8. 【Linux】- ps -ef |grep 命令

    ps:将某个进程显示出来 grep:查找 |:管道命令 表示ps命令与grep同时执行 PS是LINUX下最常用的也是非常强大的进程查看命令 grep命令是查找,是一种强大的文本搜索工具,它能使用正则 ...

  9. BZOJ 1045 糖果传递(思维)

    设第i个人给了第i+1个人mi个糖果(可以为负),因为最后每个人的糖果都会变成sum/n. 可以得到方程组 mi-mi+1=a[i+1]-sum/n.(1<=i<=n). 把方程组化为mn ...

  10. Android开发中如何解析Json

    解析Json 有了请求,自然会有响应,那我们该如何解析服务端响应返回给我们的Json数据呢? 了解什么是Json JSON(JavaScript object notation)是一种轻量级的数据交换 ...