锁优化策略

锁优化策略

提升锁性能的策略

  • 减少锁持有时间

    只在需要锁竞争的地方加入锁的操作,减少锁的占有时间,以减少线程间互斥的可能。

    public synchronized void test(){
        code1();
        mutextMethod();
        code2();
    }

    如果 code1() 方法和 code2() 方法不需要做同步控制,则会花费更多的 cup 时间。

    优化方案:只在必要时进行同步,减少线程持有锁的时间。

    public void test2(){
        code1();
        synchronized(this){
            mutextMethod();
        }
        code2();
    }
  • 减少锁粒度

    减小锁粒度也是削弱多线程竞争的有效手段。ConcurrentHashMap的实现就采用了减少锁粒度的策略,深入理解ConcurrentHashMap

  • 读写分离锁

    采用锁分离的策略,将读操作与写操作分别加锁

    读写锁的访问约束情况:

    x
    非阻塞 阻塞
    阻塞 阻塞

    如果在系统中,读操作次数远远大于写操作,则读写锁就能起到很好的作用,提升系统性能。

  • 锁分离

    将读写锁的思想进一步延伸,就是锁分离。读写锁是根据读写操作的不同进行的锁分离,我们可以根据应用的特征,使用锁分离的思想,对独占锁进行分离。

  • 锁粗化

    前面讲了尽量减少锁持有时间,但是如果对同一个锁不停地请求、同步和释放,本身也会消耗更多的系统资源。所以虚拟机在遇到一连串连续地对同一锁不断进行请求和释放的操作时,便会 把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数。

    public void test(){
        synchronized(lock){
            //do sth
        }
        //做其他不需要同步的工作,但是执行时间短
        synchronized(lock){
            //continue do sth
        }
    }
    
    JVM会整合为如下形式==>
    
    public void test(){
        synchronized(lock){
            //do sth
            //做其他不需要同步的工作,但是执行时间短
            //continue do sth
        }
    }

    实际开发中也应该衡量是否需要在合理的地方进行 锁的粗化。因为 锁粗化 实际是和 减少锁持有时间 相斥的思想,所以具体情况需要具体分析。

JVM的锁优化策略

  • 锁偏向

    核心思想:如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无需在做任何同步操作。

    对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果,因为连续多次极有可能是同一个线程请求相同的锁。而对于锁竞争比较激烈的场合,其效果不佳。因为每次都是不同的线程来请求相同的锁。

  • 轻量级锁

    如果偏向锁失败,虚拟机并不会立刻挂起线程。它会使用一种称为轻量级锁的优化手段。

    轻量级锁操作很轻便,它将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。如果线程获得轻量级锁成功,则可以顺利进入临界区。如果轻量级锁加锁失败,则表示其他线程抢到了锁,当前线程的锁请求就会膨胀为 重量级锁

  • 自旋锁

    即使锁膨胀之后,虚拟机为什么避免线程在操作系统层面挂起,会使用 自旋锁 的策略。

    由于当前线程暂时无法获得锁,但是什么时候可以活得锁是一个未知数。也许在几个CPU时钟周期后,就可以得到锁。于是,系统进行了一个赌注:假设在不久的将来,线程可以得到这把锁。因此,虚拟机会让当前线程做几个空循环,在若干次循环后,如果可以得到锁,那么顺利进入临界区。如果还不能获得锁,才会真正在系统层面挂起。

    自旋锁的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

  • 锁消除

    JVM在JIT编译时,会通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。

    public String[] createStrings(){
        Vector<String> v=new Vector<String>();
        for(int i=0;i<100;i++){
           v.add(Integer.toString(i));     
        }
        return v.toArray(new String[]{});
    }

    变量 v 只在 createString() 函数中使用,因此,它只是一个局部变量。局部变量是在线程栈上分布的,属于线程私有的数据,因此不可能被其他线程访问。所以,Vevtor内部的所有锁同步都是没有必要的,虚拟机会将这些所操作去除。

参考

  • 书 《实战Java高并发程序设计》

  • 死磕Java并发:深入分析synchronized的实现原理

你可能感兴趣的:(Java)