Synchronized和volatile以及final语义

synchronized

synchronized 是早期的线程同步的实现,通过给方法或者 代码块加上synchronized修饰 可以保证在多线程环境下只有一个线程进入到语句中执行
synchronized 是通过加入monitor(监视器) 也就说是锁,来保证线程安全的,并且获取监视器的所有权是具有互斥性,就是说只有一个线程能获取锁,另外锁也具有重入性,如果是不同的方法使用的是同一个锁对象,当前线程在进入另一个synchronized同步语句中是不需要再次获取锁
对象锁
锁住的是该类的一个对象, 任意一个对象都可以当作锁
有且只有一个获取该对象锁的线程进入synchronized同步语句
类锁
锁住的是该类,好比是个全局锁,该类所有实例对象的锁且只有一个线程能获取

对象锁 类锁
1.修饰普通方法public synchronized void method1() 1. 修饰静态方法 public static synchronized void method2()
2.修饰同步代码块 synchronized (this)

其他
JVM规范中对于monitorenter和monitorexit的描述:

  • monitorenter
    每个对象有一个监视器锁(monitor)。当monitor被占用时就处于锁定状态,线程执行monitorcenter指令时尝试获取monitor的所有权,过程如下: • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。•如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。•如果其他线程已经占用monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
  • monitorexit
    执行monitorexit的线程必须是object所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,则线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权

volatile

volatile是一个修饰变量的修饰符,volatile修饰的变量能保证多线程之间的可见性 即一个线程修改了变量 其他线程能立即感知到,但是保证不了 原子性,是一种比锁更轻量级同步实现方法,同时能够禁止指令重排序带来的线程安全问题;

指令重排
1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-LevelParallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
如果两个数据存在依赖性,则不会发生指令重排

volatile 是通过内存屏障来禁止指令重排序
内存屏障: 又称为内存栅栏,是一个cpu指令
内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:

1.阻止屏障两侧的指令重排序;
2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

LoadLoad屏障 对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕
StoreStore屏障 对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见
LoadStore屏障 对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕
StoreLoad屏障 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。
volatile 性能:

volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

final 在并发中的作用

写 final 域的重排序规则

扫描二维码关注公众号,回复: 12201260 查看本文章

写 final 域的重排序规则禁止把 final 域的写重排序到构造函数之外。这个规则的实现包含下面 2 个方面:

  1. JMM 禁止编译器把 final 域的写重排序到构造函数之外。
  2. 编译器会在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障。这个屏障禁止处理器把 final 域的写重排序到构造函数之外。

(对象逸出,或者说是 构造函数析解逸出)

写内存语义:在构造函数内对一个 final 域的写入,与随后将对象引用赋值给引用变量,这两个操作不能重排序。
读内存语义:初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作不能重排序。
【内存语义的实现原理】

在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障。
在读 final 域之前插入一个 LoadLoad 屏障。

猜你喜欢

转载自blog.csdn.net/xiaodujava/article/details/101357501
今日推荐