从JDK5引入CAS原子操作,但没有对synchronized关键字做优化,而是增加了J.U.C.concurrent,concurrent包有更好的性能;从JDK6对synchronized的实现机制进行了较大调整,包括使用JDK5引进的CAS自旋之外,还增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁这些优化策略。
  优化后的锁有四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,每种锁是只能升级,不能降级,即由偏向锁->轻量级锁->重量级锁,而这个过程就是开销逐渐加大的过程。

一、无锁状态
  线程的阻塞和唤醒需要CPU从用户态转为内核态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,另外在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的,所以尽量通过不加锁来阻塞线程的方案。

1、自旋锁
  指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

适用场景:
  自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
缺点:
  自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了CPU处理器的时间。如果持有锁的线程不会尽快释放锁,自旋的线程就会白白消耗掉处理的资源,带来性能上的浪费。
使用:
  在JDK 1.4.2中引入,默认关闭,可使用-XX:+UseSpinning开开启,在JDK1.6中默认开启;
  自旋的次数必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起,默认自旋次数为10次,通过参数-XX:PreBlockSpin来调整

2、自适应自旋锁
  自旋次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
机制:
  线程如果自旋成功了,那么下次自旋的次数会加多,JVM认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。
反之,如果对于某个锁,很少有自旋能够成功,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。
有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,JVM对程序锁的状况预测会越来越准确,JVM会变得越来越聪明。
使用:JDK1.6引入。

3、锁消除
  在有些情况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。

示例:
使用JDK的StringBuffer、Vector、HashTable等时,会存在隐形的加锁操作,比如StringBuffer的append()方法,Vector的add()方法,如果JVM根据数据流做逃逸分析之后,确定没有竞争,就会去掉加锁操作。

public void vectorTest(){
    Vector<String> vector = new Vector<String>();
    for(int i = 0 ; i < 10 ; i++){
        vector.add(i + "");
    }

    System.out.println(vector);
}

在运行这段代码时,JVM可以明显检测到变量vector没有逃逸出方法vectorTest()之外,所以JVM可以大胆地将vector内部的加锁操作消除。

4、锁粗化
  如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗,那么将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

举例:上面vector每次add的时候都需要加锁操作,JVM检测到对同一个对象(vector)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到for循环之外。

5、java对象头
  对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充:

5.1 Mark Word结构
  对象头里Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键,存储对象自身的运行时数据:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等
Mark Word被设计成一个非固定数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间。

举例32位机器对象头Mark Word结构:

5.1 Mark Word内容
  随着锁级别的不同,对象头里会存储不同的内容:

(1)对象头的最后两位存储了锁的标志位,01是初始状态,未加锁,其对象头里存储的是对象本身的哈希码;
(2)偏向锁存储的是当前占用此对象的线程ID,判断线程是否拥有锁时将线程的ID和对象头里存储的线程ID比较;
(3)而轻量级则存储指向线程栈中锁记录的指针,判断线程是否拥有锁时将线程的锁记录地址和对象头里的指针地址比较;

12-18 02:57