1.自旋锁与自适应旋锁
之前提到的互斥同步对性能最大的影响是阻塞的实现, 挂起线程和恢复线程的操作都需要转入内核态中完成, 这些操作给系统的并发性能带来了很大的压力. 但是,很多时候共享数据的锁定只会持续很短的时间,为了这段时间去挂起和恢复线程不值得.如果物理机有一个以上的CPU,能让两个或两个以上的线程同时并行执行, 我们就可以让后面请求锁的那个线程"稍等一下",但不放弃处理器的执行时间, 看看持有锁的线程是否很快就会释放锁. 为了让线程等待, 我们只需要让一个线程执行一个忙循环(自旋),这就是所谓的自旋锁
但自旋锁并不能代替阻塞, 虽然自旋锁本身避免了线程切换的开销, 但它是要占用处理器时间的. 因此,如果锁被占用的时间很短, 自旋等待的效果就会非常好. 反之.如果锁被占用的时间很长, 那么自旋只会白白浪费处理器资源. 自旋次数默认是十次
在JDK1.6中引入了自适应的自旋锁. 自适应意味着自旋的时间不再固定了, 而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定. 如果在同一个锁对象上, 自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行, 那么虚拟机认为这次自旋很有可能也会成功, 进而允许自旋等待更长时间. 另外如果对于某个所, 自旋很少成功获得过, 那么以后要获取这个锁将可能省略掉自旋过程.
2.锁消除
锁消除是虚拟机在即时编译器在运行时, 对代码上要求同步, 但是被检测到不可能存在共享数据竞争的锁进行消除.锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中, 堆上的所有数据都不会逃逸出去被其他线程访问到, 那就可以把他们当成栈上数据对待,认为他们是线程私有的, 同步加锁就无须进行.
3.锁粗化
如果一系列的连续操作都是对同一个对象反复加锁 和解锁, 甚至加锁操作是出现在循环体中的, 那即使没有线程竞争, 频繁地进行互斥同步操作 也会造成不必要的性能消耗
如果虚拟机探测到有一串零碎的操作都对同一个对象加锁, 将会把锁同步的范围扩展(粗化)到整个操作序列的外部.
4.轻量级锁
轻量级锁是相对于使用系统互斥量来实现传统锁(重量级)而言的, 轻量级锁并不是用来代替重量级锁的, 它的本意是在没有多线程竞争的前提下, 减少传统的重量级锁所使用操作系统互斥量产生的性能消耗
要理解轻量级锁, 必须从虚拟机的对象头内存布局开始讲起. 对象头分为两部分信息, 第一部分用于存储对象本身 的运行时数据,如哈希码,GC分代年龄等称为"Mark Word", 它是实现轻量级锁和偏向锁的关键. 另一部分用于指向方法区对象类型数据的指针, 如果是数组对象的话, 还会有一个额外的部分用于存储数组长度
在32位的虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希码,4bit用于存储对象分代年龄, 2bit用于存储标志位,1bit固定为0, 在其他状态(轻量级锁定, 重量级锁定,GC标记,可偏向)下对象的存储内容如下表
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码,对象分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标记 |
偏向线程ID,偏向时间戳,对象分代年龄 | 01 | 可偏向 |
轻量级锁的执行过程 : 在代码进入同步块的时候, 如果此对象没有被锁定(标记位为 01), 虚拟机首先将在当前线程的 栈帧中建立一个名为锁记录的空间, 用于存储对象目前的Mark Word的拷贝; 然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针. 如果这个更新 动作成功了, 那么这个线程就拥有了该对象的锁, 并且对象Mark Word的标志位变为"00",即表示该对象处于轻量级锁定状态. 如果这个更新操作失败了, 虚拟机, 会首先检查对象的Mark Word是否指向当前线程的栈帧, 如果说明当前线程已经拥有了这个对象的锁,那就可以直接 进入同步块继续执行; 否则说明这个对象已经被其他线程抢占了. 如果有两条以上的线程争用 同一个锁, 那轻量级锁就不再有效, 膨胀为重量级锁.Mark Word中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态
轻量级锁的解锁过程也是 通过CAS操作来完成的, 如果对象的Mark Word仍然指向线程的锁记录, 那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来, 如果替换成, 整个同步过程就完成了. 如果替换失败, 说明有其他线程尝试获取该锁, 那就要在释放锁的同时,唤醒起被挂起的线程
轻量级锁能提升 程序同步性能的依据是"对于绝大部分的锁, 在整个同步周期内都是不存在竞争的". 如果没有竞争, 轻量级 锁就避免和使用互斥同步的开销. 但如果存在锁竞争, 除了互斥量的开销外, 还额外发生了CAS操作, 因此在有竞争的情况下, 轻量级锁会比重量级锁更慢
5.偏向锁
偏向锁的目的是消除数据在无竞争情况下的同步原语, 进一步提高程序的运行性能. 如果说轻量级锁实在无竞争的情况下使用CAS去消除同步使用的互斥量, 那偏向锁就是在无竞争的情况下把整个同步都消除掉, 连CAS都不做了
偏向锁的 意思是,这个锁会偏向与第一个获得它的线程, 如果在接下来的执行过程中, 该锁没有被其他线程获取, 则持有偏向锁的线程将永远不需要进行同步
假设当前虚拟机使用了偏向锁, 那么当锁对象第一次被线程获取的时候, 虚拟机会把对象头的标志位设置为"01",并且使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word中, 如果CAS操作成功,持有偏向锁的线程以后每此进入这个锁相关的同步块时都不需要进行任何同步操作
当有另外一个线程尝试去获取这个锁时, 偏向模式宣告结束, 根据对象目前是否 处于被锁定的状态, 撤销偏向后恢复到未锁定(标志位01)或轻量级锁(状态位00)的状态,后续的操作就和前面的轻量级锁那样执行