1,volatile 实现及应用
volatile 是轻量级的 synchronized, 它在多处理器开发中保证了共享变量的 "可见性". 可见性的意思是当一个线程修改一个共享变量时, 另外一个线程能读到这个修改的值. 如果 volatile 变量修饰符使用恰当的话, 它比 synchronized 的使用和执行成本更低, 因为它不会引起线程上下文的切换和调度.
1.1,volatile 的实现原理
volatile 的定义如下:
Java 编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, 线程应该确保通过排他锁单独获得这个变量.
CPU 相关术语:
CPU 相关术语. PNG
2,synchronized 实现及应用
Java 中的每一个对象都可以作为锁. 具体表现为以下 3 种形式:
对于普通同步方法, 锁是当前实例对象.
对于静态同步方法, 锁是当前类的 Class 对象.
对于同步方法块, 锁是 Synchonized 括号里配置的对象.
从 JVM 规范中可以看到 Synchonized 在 JVM 里的实现原理, JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步, 但两者的实现细节不一样. 代码块同步是使用 monitorenter 和 monitorexit 指令实现的, 而方法同步是使用另外一种方式实现的, 细节在 JVM 规范里并没有详细说明. 但是, 方法的同步同样可以使用这两个指令来实现.
monitorenter 指令是在编译后插入到同步代码块的开始位置, 而 monitorexit 是插入到方法结束处和异常处, JVM 要保证每个 monitorenter 必须有对应的 monitorexit 与之配对. 任何对象都有一个 monitor 与之关联, 当且一个 monitor 被持有后, 它将处于锁定状态. 线程执行到 monitorenter 指令时, 将会尝试获取对象所对应的 monitor 的所有权, 即尝试获得对象的锁.
2.1,java 对象头
synchronized 用的锁是存在 Java 对象头里的. 如果对象是数组类型, 则虚拟机用 3 个字宽 (Word) 存储对象头, 如果对象是非数组类型, 则用 2 字宽存储对象头.
对象头. PNG
Java 对象头里的 Mark Word 里默认存储对象的 HashCode, 分代年龄和锁标记位.
makword.PNG
Mark Word 的状态变化:
markword 状态变化. PNG
Mark Word 的存储结构:
markword 存储结构. PNG
2.2, 锁的升级与对比
Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗, 引入了 "偏向锁" 和 "轻量级锁", 在 Java SE 1.6 中, 锁一共有 4 种状态, 级别从低到高依次是: 无锁状态, 偏向锁状态, 轻量级锁状态和重量级锁状态, 这几个状态会随着竞争情况逐渐升级. 锁可以升级但不能降级, 意味着偏向锁升级成轻量级锁后不能降级成偏向锁. 这种锁升级却不能降级的策略, 目的是为了提高获得锁和释放锁的效率.
2.2.1, 偏向锁
大多数情况下, 锁不仅不存在多线程竞争, 而且总是由同一线程多次获得, 为了让线程获得锁的代价更低而引入了偏向锁. 当一个线程访问同步块并获取锁时, 会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID, 以后该线程在进入和退出同步块时不需要进行 CAS 操作来加锁和解锁, 只需简单地测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁. 如果测试成功, 表示线程已经获得了锁. 如果测试失败, 则需要再测试一下 Mark Word 中偏向锁的标识是否设置成 1(表示当前是偏向锁): 如果没有设置, 则使用 CAS 竞争锁; 如果设置了, 则尝试使用 CAS 将对象头的偏向锁指向当前线程.
偏向锁的撤销:
偏向锁使用了一种等到竞争出现才释放锁的机制, 所以当其他线程尝试竞争偏向锁时, 持有偏向锁的线程才会释放锁. 偏向锁的撤销, 需要等待全局安全点(在这个时间点上没有正在执行的字节码). 它会首先暂停拥有偏向锁的线程, 然后检查持有偏向锁的线程是否活着, 如果线程不处于活动状态, 则将对象头设置成无锁状态; 如果线程仍然活着, 拥有偏向锁的栈会被执行, 遍历偏向对象的锁记录, 栈中的锁记录和对象头的 Mark Word 要么重新偏向于其他线程, 要么恢复到无锁或者标记对象不适合作为偏向锁, 最后唤醒暂停的线程.
偏向锁获取及撤销流程. PNG
2.2.2, 轻量级锁
轻量级锁加锁:
线程在执行同步块之前, JVM 会先在当前线程的栈桢中创建用于存储锁记录的空间, 并将对象头中的 Mark Word 复制到锁记录中, 官方称为 Displaced Mark Word. 然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针. 如果成功, 当前线程获得锁, 如果失败, 表示其他线程竞争锁, 当前线程便尝试使用自旋来获取锁.
轻量级锁解锁:
轻量级解锁时, 会使用原子的 CAS 操作将 Displaced Mark Word 替换回到对象头, 如果成功, 则表示没有竞争发生. 如果失败, 表示当前锁存在竞争, 锁就会膨胀成重量级锁.
轻量级锁及膨胀流程. PNG
因为自旋会消耗 CPU, 为了避免无用的自旋(比如获得锁的线程被阻塞住了), 一旦锁升级成重量级锁, 就不会再恢复到轻量级锁状态. 当锁处于这个状态下, 其他线程试图获取锁时, 都会被阻塞住, 当持有锁的线程释放锁之后会唤醒这些线程, 被唤醒的线程就会进行新一轮的夺锁之争.
锁的优缺点对比:
来源: http://www.jianshu.com/p/cd9e9277084d