A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

概述
多线程编程的三个问题:可见性、有序性及原子性。volatile关键字:

保证多线程环境下的可见性
通过防止重排序解决有序性
对volatile变量的单次读/写操作可保证原子性,如long和double类型变量,但不能保证i++这种操作的原子性,因i++是读、写两次操作
可见性
定义:一个线程对共享变量做修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。

JMM规定所有的变量都是存在主内存中,每个线程都有自己的工作内存。每个线程对共享数据的读、写操作都只在各自的工作内存中,不能直接在主内存中进行;在各自工作内存中对数据操作完成后,同步到主内存中;线程间不能访问各自工作内存中的数据,只能通过主内存来完成。

happens-before原则禁止某些处理器和编译器的重排序,也可以保证可见性。

实现可见性的内存语义:
当写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存。
当读一个volatile变量时,JMM会把线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。

主内存:所有线程共享的区域,存储线程共享的数据,包括实例变量、静态变量和构成数组的对象的元素,不包括局部变量和方法参数。
工作内存:每个线程独享的区域,存储主内存中的数据拷贝。

实现可见性的方法:volatile、synchronized、Lock、final。

有序性
定义:

重排序:JVM或JIT为了获得更好的性能,在不影响语句执行结果(即as-if-serial)的前提下会对语句重排序。但volatile类型变量因内存屏障和happens-before规则,则不会参与重排序。

volatile禁止指令重排序是通过lock前缀指令实现,相当于一个内存屏障,指令重排序时不能把后面的指令重排序到lock前缀指令之前,强制将对工作内存的修改操作立即写入主内存中。只有一个cpu时,这种内存屏障是多余的。多个cpu访问同一块内存时,需要内存屏障。

单例模式的一种实践,DLC,double lock check,双重检查加锁,便是利用这个特性。

原子性
定义:指一个操作不能被打断,要么全部执行完毕,要么不执行。

Java中读取long/double类型变量不是原子的,需要分成两步,高32位和低32位。对一个volatile型的 long或double变量的读写是原子。对于++整个操作不是原子性的。++操作,可以用AtomicInteger.incrementAndGet()来代替;Atomic**采用基于CAS的无锁技术。

拓展
happens-before
保证正确同步的多线程程序的执行结果不被改变。
规则:

程序顺序规则(Program Order Rule):一个线程中的每个操作,happens-before于该线程中的任意后续操作。
监视器锁规则(Monitor Lock Rule):解锁,happens-before于加锁,强调的是同一个锁。
volatile变量规则(Volatile Variable Rule):对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性(Transitivity):如果A happens-before B, 且B happens-before C, 那么A happens-before C。
线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
线程终于规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,可以通过Thread.join()方法结束,Thread.isAlive()的返回值等作段检测到线程已经终止执行。如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生。
对象终结规则(Finalizer Rule):一个对象初始化完成(构造方法执行完成)先行发生于它的finalize()方法的开始
as-if-serial
保证单线程内程序的执行结果不被改变;不管怎么重排序,单线程程序的执行结果不能改变。编译器、runtime和处理器都必须遵守as-if-serial语义。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序。
as-if-serial语义把单线程程序保护起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

重排序
指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。重排序分3种类型:

编译器优化的重排序。编译器在不改变单线程程序语义(as-if-serial )的前提下,可以重新安排语句的执行顺序
指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对机器指令的执行顺序
内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序:

上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序可能会导致多线程程序出现内存可见性问题。对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。

重排序的意义:JVM能根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能!

内存屏障
Memory Barrier,也叫内存栅栏,Memory Fence,是一组CPU指令,用于控制特定条件下的重排序和内存可见性问题。特殊指令,如ARM中的dmb、dsb和isb指令,x86中的sfence、lfence和mfence指令。CPU遇到这些特殊指令后,要等待前面的指令执行完成才执行后面的指令。

Java编译器也会根据内存屏障的规则禁止重排序。
分为以下几种类型:

LoadLoad屏障:对于语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
有的处理器的重排序规则较严,无需内存屏障也能很好的工作,Java编译器会在这种情况下不放置内存屏障。

Java编译器使用内存屏障:

内存屏障阻碍CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失。为了达到最佳性能,最好是把要解决的问题模块化,这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障。采用这个方法可以让处理器不受限的执行一个任务单元。合理的内存屏障组合的好处:缓冲区在第一次被刷后开销会减少,因为再填充改缓冲区不需要额外工作。

注意
使用volatile修饰的变量必须满足以下两个条件:

对变量的写操作不依赖于当前值,或确保只有一个线程修改变量的值
该变量没有包含在具有其他变量的不变式中。
这两个条件也是并发环境的线程安全的保证。

volatile vs synchronzied
volatile作为一种轻量级的同步工具,一般而言比synchronzied拥有更少的资源消耗。因为JDK6以后的版本对synchronzied进行锁消除和优化。

加volatile与不加volatile比较,性能消耗读操作时没有任何区别,写操作因为需要插入内存屏障指令来保证多个cpu下不会发生乱序操作,性能会略有降低,是值得的。

其他
Java 中能创建 Volatile 数组吗?
能,不过只是一个指向数组的引用,而不是整个数组,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。

文章转载自https://blog.csdn.net/lonelymanontheway/article/details/105856698

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马