1、线程安全的实现方法
1.1 互斥/同步
synchronized/java.util.concurrect包
1.2 非阻塞同步
CAS操作 如原子变量操作
1.3 无同步方案
可重入代码:如果一个方法,它的返回结果是可预测的,只要输入了相同的数据,就都能返回相同的结果,就是可重入的。
线程本地存储:ThreadLocal
2、锁优化
2.1 自旋锁/自适应自旋锁
互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成。自旋锁就是让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区
自适应自旋锁:如果锁被占用的时间很短,效果很好。如果很长,自旋会浪费cpu资源。因此,自旋超过了限定次数仍没获得锁,就会用传统的方式挂起线程。自适应就是由上一次在同一个锁上自旋时间以及锁的拥有者的状态来决定自旋的时间
2.2 锁消除
被检测到不可能存在共享数据竞争的锁进行消除。许多锁是自动加入的,对于不需要的锁进行消除,提高效率。
2.3 锁粗化
同步块的范围应该尽量小,但是如果同步块在循环体中,频繁的加锁、解锁反而会导致不必要的性能损耗
2.4 轻量级锁
轻量级锁是JDK1.6之后加入的。轻量级锁不是用来替代重量级锁(传统的锁,互斥等),它的本意是在没有多线程竞争的情况下,减少重量级锁使用操作系统互斥量产生的性能消耗
轻量级锁的信息放在消息头的Mark Word中
代码进入同步块时,如果同步对象没有加锁(锁标志位为01),虚拟机首先在当前线程的栈帧建立一个锁记录的空间,用于存储当前Mark Word的拷贝。然后虚拟机使用CAS操作尝试将对象的Mark Word更新为指向当前线程的锁记录空间
如果成功,线程拥有该对象的锁,并且Mark Word的标记更新为00
如果失败,则检查Mark Word是否指向当前线程的栈帧,如果指向当前线程,说明线程拥有锁,继续向下执行
如果没有指向当前线程,说明被其他线程抢占。轻量级锁将膨胀为重量级锁,锁标志位更新为10,Mark word存储的指针指向重量级锁,后续线程阻塞。
解锁过程也是通过CAS,如果对象的Mark Word仍然指向当前线程的锁记录,就将栈帧中加锁时保存的Mark Word和对象的Mark Word替换回来。
如果替换成功,整个同步完成。
如果失败,说明有其他线程尝试获取该锁,那就在释放锁的同时,唤醒被挂起的线程
如果对象没有多线程下的竞争,轻量级锁使用CAS避免了使用互斥量的开销。
但如果存在竞争,除了互斥量的开销,还需要CAS操作。
因此在竞争的情况下,轻量级锁会比重量级锁慢
2.5 偏向锁
锁对象第一次被线程获取时,虚拟机将Mark Word的标志位设置为01 即偏向锁,同时用CAS将线程ID存放到MarkWord中。
如果成功,线程下次再进入时,比较一下线程ID,如果相同,则不需要任何同步操作。
如果有另一个线程尝试获取这个锁时,偏向锁将撤销,然后恢复到未锁定或轻量级锁状态
一般偏向锁是在有不同线程申请锁时升级为轻量锁,这也就意味着假如一个对象先被线程1加锁解锁,再被线程2加锁解锁,这过程中没有锁冲突,也一样会发生偏向锁失效,不同的是这回要先退化为无锁的状态,再加轻量锁
如果偏向锁已经被锁定 则直接升级为轻量级锁