JVM锁与锁优化

编码中进行锁优化:

  1. 锁细化,减少锁持有的时间,在一个线程的代码之中,尽量不要对一个方法进行加锁,而是抽离出一个方法中的共享数据,只对方法中的这部分数据代码加锁,这样能减少加锁范围。防止在出现多线程竞争的时候,多个线程对同一个锁进行竞争,而持有锁的代码过长导致执行时间很长,处于阻塞状态的线程等待时间过长,阻塞状态的线程所处理的业务响应速度下降。
  2. 锁分离,根据代码同步操作的性质,分离锁,比如说,在某些代码的同步性质之下,读数据的操作和写数据的操作之间存在互斥,而读数据之间没有互斥,写数据之间没有互斥,因此可以将读数据和写数据分离出来,让读数据和写数据互斥,而读数据之内或者写数据之内没有互斥。
  3. 锁粗化,增加锁持有的时间,这和锁细化是不矛盾的。锁粗化和锁细化锁面向的问题不一样。在同一段代码之中,如果有一个线程频繁地进行加锁和解锁的操作,甚至是同一个线程对同一个对象进行加锁和解锁,这时频繁的加解锁会造成不必要的消耗,这些加解锁操作可以通过锁粗化去除,也就是将加解锁的范围扩大,让这段代码执行一次加解锁的操作。例如StringBuffer(StringBuffer是线程安全的,这个类出现的早,StringBuilder是非线程安全的,这个类出现的晚,应该是为了提高在非线程安全情况下的执行速度而设计的)中的append()方法,其方法为

         public synchronized StringBuffer append(xxx xxx),如果在一段代码中连续使用append方法,则会导致每次append方法都会伴随一次加解锁操作,连续的使用会有连续的加解锁,性能浪费,因此可以将这一系列append操作加入一个同步代码块中。

JVM存在的锁优化:

  1. 锁粗化,类似于我们在编程时进行的锁粗化优化,JVM会对代码进行锁粗化优化。
  2. 锁消除,类似于我们在编程时,如果一段代码不存在多线程竞争,那么就不需要进行同步,也不需要进行锁操作,JVM也通过代码逃逸技术,判断堆上数据是多线程共享的,还是局限在单一线程之内,不会逃逸出某一线程的,如果根本不会被其他线程使用,则就是不需要同步的,锁也就没必要加。JVM会将代码中的这种锁进行消除。
  3. 自旋锁,由于代码中的锁往往被一个线程持有较短的时间,因此当线程申请锁的时候,锁如果被占用了,则让当前线程执行一个自旋(忙循环),看看持有锁的线程能否很快释放锁,如果超过自旋允许的范围还没获得锁,才会进入同步阻塞状态。这样可以在某些情况下减少进入同步阻塞,提升效能,因为同步阻塞状态的时间消耗比较大,对于一些代码需要很高的响应速度,减少同步阻塞的进入是很有必要的。适应性自旋锁,是对自旋锁的改进,简单来说就是当前自旋锁自旋成功后,下次自旋执行的次数或者说时间就会长一些,当前自旋锁执行失败,下次自旋执行的次数会少一些。
  4. 偏向锁,引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS(比较和交换操作)原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。锁对象偏向于当前获得该锁对象的线程,如果接下来没有其它线程请求获得该锁对象(也就是只有一个线程在执行同步块,会有这样的疑问,既然只有一个线程会执行同步块,为何还有进行同步?产生这样疑问的原因是考虑的角度是编码人员的锁优化,但是这是JVM层面的自动锁优化,当一段代码需要同步时,我们对其进行synchronized同步,而程序在执行的时候并不是一直处于多线程竞争的情况,JVM会利用偏向锁将单一线程执行同步代码块的执行时间进行压缩(相对于轻量级锁和重量级锁执行时间短了)),则持有该锁对象的线程不需要进行同步操作,不需要轻量级锁加解锁,更不需要重量级锁加解锁,没有同步加解锁的代码执行速度快很多;如果出现一个线程申请获得该锁对象,则偏向模式结束,很多设置下会进入轻量级加解锁。
  5. 轻量级锁,“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。轻量级锁的加解锁避免了进入重量级锁的加解锁,即操作系统级别的互斥操作,而是利用CAS操作进行轻量级锁的加解锁,利用轻量级锁完成同步。

         JVM锁优化的思路:

         Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。

         JVM对于锁的优化方向是将代码中不需要执行重量级锁的代码进行优化,使用其他锁完成这些代码,尽量减少重量级锁的使用,从而提高执行效率。各种非重量级锁均不是新的可以替代重量级锁的锁,而都是在重量级锁的基础之上,对其优化,尽量减少重量级锁的使用。

猜你喜欢

转载自blog.csdn.net/E_N_T_J/article/details/88101663