美文网首页
重温系列之多线程并发:锁的优化

重温系列之多线程并发:锁的优化

作者: 内卷星球 | 来源:发表于2019-04-06 14:22 被阅读0次

    减少锁持有时间

    只用在需要线程安全的方法上加锁

    减小锁粒度

    将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是ConcurrentHashMap。

    锁分离

    最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如下面的LinkedBlockingQueue 从头部取出,从尾部放数据


    image

    锁粗化

    通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。

    public void demoMethod(){  
            synchronized(lock){   
                //do sth.  
            }  
            //做其他不需要的同步的工作,但能很快执行完毕  
            synchronized(lock){   
                //do sth.  
            } 
    }
    

    这种情况,根据锁粗化的思想,应该合并

    public void demoMethod(){  
            //整合成一次锁请求 
            synchronized(lock){   
                //do sth.   
                //做其他不需要的同步的工作,但能很快执行完毕  
            }
    }
    

    当然这是有前提的,前提就是中间的那些不需要同步的工作是很快执行完成的。

    锁消除

    在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

    也许你会觉得奇怪,既然有些对象不可能被多线程访问,那为什么要加锁呢?写代码时直接不加锁不就好了。
    但是有时,这些锁并不是程序员所写的,有的是JDK实现中就有锁的,比如Vector和StringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。

    public static void main(String args[]) throws InterruptedException {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 2000000; i++) {
                createStringBuffer("JVM", "Diagnosis");
            }
            long bufferCost = System.currentTimeMillis() - start;
            System.out.println("craeteStringBuffer: " + bufferCost + " ms");
        }
    
        public static String createStringBuffer(String s1, String s2) {
            StringBuffer sb = new StringBuffer();
            sb.append(s1);
            sb.append(s2);
            return sb.toString();
        }
    

    StringBuffer.append是一个同步操作,但是StringBuffer却是一个局部变量,并且方法也并没有把StringBuffer返回,所以不可能会有多线程去访问它,那么此时StringBuffer中的同步操作就是没有意义的。

    开启锁消除是在JVM参数上设置的,当然需要在server模式下:

    -server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
    

    并且要开启逃逸分析
    逃逸分析的作用呢,就是看看变量是否有可能逃出作用域的范围:
    比如上述的StringBuffer,上述代码中craeteStringBuffer的返回是一个String,所以这个局部变量StringBuffer在其他地方都不会被使用。如果将craeteStringBuffer改成

    public static StringBuffer craeteStringBuffer(String s1, String s2) {
            StringBuffer sb = new StringBuffer();
            sb.append(s1);
            sb.append(s2);
            return sb;
    }
    

    那么这个 StringBuffer被返回后,是有可能被任何其他地方所使用的(譬如被主函数将返回结果put进map啊等等)。那么JVM的逃逸分析可以分析出,这个局部变量 StringBuffer逃出了它的作用域。
    所以基于逃逸分析,JVM可以判断,如果这个局部变量StringBuffer并没有逃出它的作用域,那么可以确定这个StringBuffer并不会被多线程所访问,那么就可以把这些多余的锁给去掉来提高性能

    相关文章

      网友评论

          本文标题:重温系列之多线程并发:锁的优化

          本文链接:https://www.haomeiwen.com/subject/lmevbqtx.html