volatile关键字
- 保证共享变量的可见性
使用Lock指令保证可见性
a.引起处理器缓存回写到内存
b.处理器缓存回写,会导致其它处理器的缓存无效
- 防止编译器和处理器的重排序
为了实现volatile的内存语义,编译器在生成字节码时,在指令序列中插入了内存屏障
- 部分原子性,但是volatile++这种复合操作不具有原子性
案例:
- 多线程计数器,操作volatile变量a++,因为++操作依赖于a的旧值,所以这个操作线程不安全,证明volatile无法保证原子性
- 一个线程读volatile变量b,判断b==true来确定是否执行下一步,一个线程写b = true;因为b的值不依赖与旧值,所以这个是线程安全的。
- 经典的单例双重检测(DCL: Double Check Lock)的代码,单例变量之所以使用volatile,就是为了禁止重排序。
final关键字
final可以修饰类、方法、变量
- final变量:被final修饰的基础类型变量不可变,被final修饰的引用类型变量引用不可变,但引用对象的内容可以变。final变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
- final方法:方法前面加上final关键字,代表这个方法不可以被子类的方法重写。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。
- fianl类:final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String, Interger以及其他包装类。同样,final类没有动态绑定所以性能好。
final作用
- final关键字提高了性能,JVM和Java应用都会缓存final变量、方法及类进行优化。
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
- 创建不可变类
final域的内存语义:
对于final域,编译器和处理器要遵守两个重排序规则:
- 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
- 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
这个规则防止了final域在初始化前被其它线程访问,即所有线程都能看到fianl域在构造函数中被初始化之后的值。同时我们知道final域必须在声明或构造方法里面初始化,所以说,final域是线程安全的。
synchronized关键字
synchronized的同步是使用monitorenter和monitorexit指令实现的
concurrent包的实现
使用volatile和CAS组合的模式来实现线程之间的通讯,AQS、非阻塞数据结构、原子变量类等,这些基础的concurrent都是基于这个模式来实现的,而concurrent包的高层类又是依赖于这些基础类来实现。
AQS(AbstractQueuedSynchronizer)
AQS简称同步器,是用来构建锁和其它同步组件的底层同步类,它使用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,简化了锁和同步组件的实现方式,屏蔽了同步状态管理、线程排队、等待和唤醒等底层操作。
AQS主要使用方式是通过继承,实现AQS的模版方法,然后将子类作为自定义同步组件的静态内部类。
java.util.concurrent中的Semaphore、ReentrantReadWriteLock、ReentrantLock,CountdowLatch、SynchronousQueue和FutureTask都是基于AQS实现的。
- AQS抽象类使用一个volatile的int全局变量来代表同步状态(或锁状态)
private volatile int state;
-
AQS抽象类维护了一个FIFO的双向队列来管理线程
AQS抽象类提供了一系列模板方法:
独占式:tryAcquire()和tryRelease()
共享式:tryAcquireShared()和tryReleaseShared()
- AQS的线程阻塞和唤醒使用LockSupport工具类完成,LockSupport使用本地方法unsaft.part/unsaft.unpart来阻塞和唤醒线程。
-
AQS抽象类的内部类ConditionObject,维护了一个等待队列,提供与对象监视器类似的监视器方法,实现线程的等待/通知模式。
CAS
CAS的问题
- 只能保证对一个变量保证原子性,AtomicReference可以解决
- 失败重试,会引起自旋,消耗cpu,处理器提供的pause指令可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源。
- ABA问题,AtomicStampedReference使用时间戳解决、AtomicMarkableReference使用boolean变量解决。
CAS与Synchronized的使用情景:
1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
sun.misc.Unsafe类里面提供了一大堆本地CAS方法。
CAS的用法:
//常规的CAS代码,可以参考ConcurrentHashMap源码的initTable()方法
//循环
while(true){
if(条件不满足){
break;
}
//乐观锁,防止多线程同时进入
if(CAS(旧值,副本,新值)){
if(条件满足){
doSomething();//执行操作
}
break;
}
}