volatile:
使用volatile修饰的共享变量在写操作的时候会多出Lock指令,会立即引发两种效果:
- 将当前变量的值从工作内存写会到主内存中
- 其他CPU里面缓存的该变量的值会置为无效(由缓存一致性协议保障)
---- 锁缓存的开销比锁总线的开销要少很多,锁总线意味着不能访问系统内存!
3.禁止读和写的重排序 – 双重锁问题就是通过这个解决的!(第一个操作是volatile写(A),第二个操作是volatile读/写(B),不能重排序)
Synchronize:
一般有三种使用方式:
- 普通方法 – 锁的是当前对象(对象锁)
- 静态方法 – 锁的是class对象 (类锁)
---- 一个类不管被new多少次,静态变量/方法在内存中都只有一个份!
3.静态代码块 – 锁的是代码块里的资源
编译的字节码中会多出monitorent/momitorexit指令,它们修饰之间的内容会被视为锁定,线程拥有对象对应的monitor才能执行指令之间的代码.
个人的感觉是,在如今synchronized进行了大量优化之后,volatile几乎很少使用,使如果使用到volatile要特别注意它的语义
final: 语义是在处理器中实现的
final的用法一般有一下几种:
- 用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;
- 用来修饰方法参数,表示在变量的生存期中,参数的值不能被改变;
- 修饰方法,表示该方法无法被重写;
- 修饰类,表示该类无法被继承。
final在同步中的作用会经常被忽视,实际上final可以做出如下保证:
当创建一个对象时,使用final关键字能够使别的线程不能访问到处于”部分创建”的对象。保证对象的安全发布,即不会出现对象创建到一半,别的线程就拿着这个引用去乱用。
final域对于编译器和处理器来说有两个重排序规则:
- 写: final域写入和对象引用赋值这两个操作不能重排序 – 保证创建的顺序性
- 读: 初次读对象的引用和里面的final域不能重排序 – 保证读的顺序性
- 避免一个对象的引用被两个线程持有,然后一个线程读,一个线程写,不用final可能会出现脏读的情况
注意: final无法解决双重锁问题,因为双重锁问题的问题在于B线程拿到引用实际上没有被A线程完全初始化完毕,此时还没有操作到对象里面的变量.