synchronized的四种状态
无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向:无锁→偏向锁→轻量级锁→重量级锁
偏向锁:减少同一线程获取锁的代价
多数情况下,锁不存在多线程竞争,总是由同一线程获得
如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构就变成了偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,获取锁的过程只需要检测Mark Word的锁标记位为偏向锁以及当前线程id等于Mark Word的ThreadID即可,节省了大量有关申请锁的操作
轻量级锁:
由偏向锁升级而来,偏向锁运行在一个线程进入同步代码快的情况下,当第二个线程加入锁争用的时候,偏向锁会升级为轻量级锁
适用于线程交替执行同步块的情况下,加锁解锁使用到CAS操作
重量级锁:
存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁
直接阻塞线程
锁的内存语义:
当线程释放锁的时候,java内存模型会把线程对应的本地内存中的共享变量刷新到主内存中
当线程获取锁的时候,java内存模型就会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主存中读取共享变量
各类锁的优缺点
其他优化示例:
自旋锁
多数情况下,共享数据的锁定状态持续时间较短,切换线程会带来大量的系统资源消耗
通过让线程执行忙循环来等待锁的释放,不让出CPU
缺点:若锁被其他线程长时间占用,会带来许多性能上的开销,因为在线程自旋的时候,会始终占用CPU的时间片,浪费资源。但是可以让线程在执行忙循环一定次数后停止,可以通过PreBlockSpin来进行修改
自适应自旋锁
自旋的次数不固定,由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来绝定。(比如,如果上一次自旋获得可锁,那么JVM会认为这一次也很有可能能够获取到锁,所以减少锁忙循环的次数,如果很少获得锁,那么以后可能会减少甚至消除锁循环)
锁消除
JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁
比如StringBuffer是线程安全的,但是由于stringBuffer只会在append方法中使用,因此它不可能是共享的资源,JVM会自动消除内部的锁
锁粗化
通过扩大加锁的范围,避免反复的加锁解锁
由于反复使用了append这个同步方法,JVM就会自动识别到,并且将加锁的范围加到整个加锁方法的外部,就只需要加一次锁