关键字volatile
1.volatile的定义
JAVA编程语言允许线程访问共享变量,为了确保共享变量能够被准备和一致地更新,线程应该确保通过排它锁单独获得这个变量。
2.对有volatile变量修饰的共享变量进行写操作会汇编指令多出一行代码:Lock前缀指令。Lock前缀的指令在多核处理器下会引发以下两件事情
1.将当前处理器缓存行的数据回写到系统内存。
2.这个写回内存操作会使得其他CPU里缓存了该内存地址的数据无效。
3.如果对volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的命令,将这个变量所在缓存行的数据写回到系统内存中。
4.volatile的使用优化
追加字节优化性能:将共享变量追加到64字节。
理由:如果队列的头节点和尾节点都不足64个字节,处理器会将它们读到同一个高速缓存行里面。在多处理器的情况下每个处理器都会缓存同样的头尾节点,当一个处理器师徒修改头节点时,将锁定整个缓存行,在缓存一致性机制的作用下,导致其他处理器不能访问自己告诉缓存中的尾节点,严重影响到队列的出队和入队的效率。
注意:在以下情况下不应该将volatile变量追加到64字节
- 缓存行非64字节宽的处理器
- 共享变量不会被频繁写地(因为本身追加字节这种方式会带来一定的性能消耗)
关键字synchronized
1.Java的每一个对象都可以作为锁
1. 对于普通同步方法,锁是当前实例对象;
2. 对于静态同步方法,锁是当前类的Class对象;
3. 对于同步方法快,锁是Synchronized括号里面配置的对象。
2.JAVA用的锁存在Java对象头里。
如果对象是数组类型,那么虚拟机就用3个字宽(Word)存储对象头;如果对象是非数组类型,则用2字宽存储对象头。因为如果是一个数组的话需要多一个字宽来表示数组的长度。
3.锁一共有四种状态,从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。锁可以升级但是不可以降级,这样的目的是为了提高获得和释放锁的效率。
1.偏向锁:会偏向第一个访问锁的线程,如果在接下来的过程中,该锁不会被其他线程访问,则持有该偏向锁的线程永远不需要触发同步。如果遇到其他线程抢占,那么持有偏向锁的线程将被挂起,JVM尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只在单线程下起作用。)(如果确定应用的所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁。)
2.轻量级锁:为了优化Java的Lock机制才引进的,本意是为了减少多线程进入互斥的几率而不是替代互斥。
- 轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中,创建用于存储锁记录的空间,将对象头中得Mark Word复制到锁记录中,然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁。如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获得锁。
- 轻量级锁解锁:使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生,如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。因为自旋会消耗CPU,为避免无用的自旋,一但升级成为重量级锁,就不会再恢复到轻量级锁状态。
关于锁的优缺点
4.处理器如何实现原子操作
1.通过总线锁保证原子性:当处理器输出一个Lock#信号,其他处理器的请求被阻塞,那么该处理器将可以独占共享内存。
2.通过缓存锁定保证原子性:内存区域如果被缓存在处理器的缓存行里,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上发LOCK#信号,而是修改内部的地址,并允许它的缓存一次性机制保证操作的原子性,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效。
两种情况下处理器不会使用缓存锁定:
- 当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,处理器会调用总线锁定;
- 有些处理器不支持缓存锁定。
5.CAS实现原子操作的三个问题:
- ABA问题(数值没变,但是版本变了);
- 循环时间长开销大(pause指令);
- 只能保证一个共享变量的原子操作(对多个共享变量操作,我们可以把多个共享变量合并成一个共享变量来操作)。