synchronized的四种状态

无锁、偏向锁、轻量级锁、重量级锁

锁膨胀方向无锁→偏向锁→轻量级锁→重量级锁

 

偏向锁:减少同一线程获取锁的代价

多数情况下,锁不存在多线程竞争,总是由同一线程获得

如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构就变成了偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,获取锁的过程只需要检测Mark Word的锁标记位为偏向锁以及当前线程id等于Mark Word的ThreadID即可,节省了大量有关申请锁的操作

轻量级锁:

由偏向锁升级而来,偏向锁运行在一个线程进入同步代码快的情况下,当第二个线程加入锁争用的时候,偏向锁会升级为轻量级锁

适用于线程交替执行同步块的情况下,加锁解锁使用到CAS操作

重量级锁:

存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁

直接阻塞线程

锁的内存语义:

线程释放锁的时候,java内存模型会把线程对应的本地内存中的共享变量刷新到主内存中

线程获取锁的时候,java内存模型就会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主存中读取共享变量

各类锁的优缺点

 

其他优化示例:

自旋锁

多数情况下,共享数据的锁定状态持续时间较短,切换线程会带来大量的系统资源消耗

通过让线程执行忙循环来等待锁的释放,不让出CPU

缺点:若锁被其他线程长时间占用,会带来许多性能上的开销,因为在线程自旋的时候,会始终占用CPU的时间片,浪费资源。但是可以让线程在执行忙循环一定次数后停止,可以通过PreBlockSpin来进行修改

自适应自旋锁

自旋的次数不固定,由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来绝定。(比如,如果上一次自旋获得可锁,那么JVM会认为这一次也很有可能能够获取到锁,所以减少锁忙循环的次数,如果很少获得锁,那么以后可能会减少甚至消除锁循环)

锁消除

JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁

比如StringBuffer是线程安全的,但是由于stringBuffer只会在append方法中使用,因此它不可能是共享的资源,JVM会自动消除内部的锁

锁粗化

通过扩大加锁的范围,避免反复的加锁解锁

由于反复使用了append这个同步方法,JVM就会自动识别到,并且将加锁的范围加到整个加锁方法的外部,就只需要加一次锁