彻底搞懂volatile和synchronized

1,262 阅读5分钟

瓜1.PNG 这是我参与更文挑战的第4天,活动详情查看:更文挑战

Java线程在第一次读取共享属性之前,会从主内存中复制这个属性到线程的工作内存中,之后才会操作这个共享属性,在操作完成之后,如果共享属性的值发生了修改,则会先保存到本地的工作线程,然后才会刷新到主内存。必要了解三个名词:原子性、可视性和有序性。

原子性:一个操作/一系列操作要么全部执行要么都不执行

可视性:如果一个线程对一个共享值作出了一些修改,其他线程都可以看到这个共享值的修改。

有序性:程序的运行顺序要和程序的逻辑顺序一致,可能实际情况是计算机考虑到性能的因素执行顺序有所不同,但是结果肯定会跟逻辑顺序一致。

volatile

当一个变量是共享变量,同时被volatile修饰当值被更改的时候,其他线程再读取该变量的时候可以保证能获取到修改后的值,通过JMM屏蔽掉各种硬件和操作系统的内存访问差异以及CPU多级缓存等导致的数据不一致问题。需要注意的是,volatile 修饰的变量对所有线程是立即可见的,关键字本身就包含了禁止指令重排的语意,但是在非原子操作的并发读写中是不安全的,比如i++操作一共分三步操作。相比synchronised Lock, volatile更加轻量级,不会发生上下文切换等开销. volatile的作用:保证了可见性和有序性(不保证原子性),如果一个共享变量被volatile关键字修饰,那么如果一个线程修改了这个共享变量后,其他线程是立马可知的。举例:线程A修改了自己的共享变量副本,这时如果该共享变量没有被volatile修饰,那么本次修改不一定会马上将修改结果刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是没有被A修改之前的值。如果该共享变量被volatile修饰了,那么本次修改结果会强制立刻刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是被A修改之后的值了。volatile能禁止指令重新排序,在指令重排序优化时,在volatile变量之前的指令不能在volatile之后执行,在volatile之后的指令也不能在volatile之前执行,所以它保证了有序性。

synchronized

在Java中,所有实例对象都自动含有单一的锁(也称监视器)。所以,当使用synchronized修饰的方法的时候,该方法会自动给实例加锁,Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。进入由Synchronized 保护的代码区首先需要获取 Synchronized 这把锁,其他线程想要执行必须进行等待。Synchronized 锁住的代码区域执行完成后需要把锁归还,也就是释放锁,这样才能够让其他线程使用。简单来说,就是同一时间内,只能有一个线程访问 synchronized修饰的方法或者代码块啊,保证了原子性、有序性和可视性。

互斥是实现同步的一种手段,临界区、互斥量(Mutex)和信号量(Semaphore)都是主要互斥方式。互斥是因,同步是果。监视器锁(Monitor 另一个名字叫管程)本质是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

在 Java 虚拟机 (HotSpot) 中,Monitor 是基于 C++ 实现的,由 ObjectMonitor 实现的, 几个关键属性: _owner:指向持有 ObjectMonitor 对象的线程 _WaitSet:存放处于 wait 状态的线程队列 _EntryList:存放处于等待锁 block 状态的线程队列 _recursions:锁的重入次数 count:用来记录该线程获取锁的次数

volatile和synchronized的区别

volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 volatile只限于使用在变量级别;synchronized则可以使用在变量、方法、和类级别的 volatile只限于实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性 volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

瓜2.PNG