Java 锁介绍


JVM中的锁
        
        每个对象都有一个对象头mark 4个字节(32位),
        mark主要存放:
                对象的hash, 锁信息, 垃圾回收标记, 对象年龄
                        指向锁记录的指针
                        指向monitor的指针(synchronized关键字对应的指令码)
                        偏向锁线程id
        
        偏向锁
            偏向锁是一个线程获取锁以后,当该线程再次获取该锁, 不需要做monitor等判断, 可以直接获取, 性能较高.
            偏向锁使用只在资源不存在或者存在十分小的竞争情况下使用,能有效提高性能,
            在资源竞争激烈(多线程)的情况下, 每次线程都试图获取偏向锁,但是每次都获取不到, 所以性能下降.
            偏向锁在jdk1.6开始默认启用, 参数为:-XX:+UseBiasedLocking
                -XX:BiasedLockingStartupDelay=n    为偏向锁启用时间, 默认会在jvm启动几秒后启用, jvm团队认为          jvm刚启动时, 资源竞争高,所以会在后续几秒后启用.
        
        轻量级锁(BasicObjectLock)
            嵌入在线程栈中的对象
            BasicObjectLock有(BasicLock)对象头和指向持有该锁的对象指针组成.
            普通的锁性能不理想, 轻量级锁是一种快速锁定方法.
            使用轻量级锁, 在判断是否一个线程只有对象锁, 只需要判断对象头的锁指针是否指向该线程栈即可.
            
            轻量级锁失败,表示存在竞争, 升级为重量级锁(常规锁)
            在没有锁竞争情况下, 减少传统互斥量产生的性能消耗
            在激烈竞争时, 轻量级锁会多做额外操作, 导致性能下降
            

        自旋锁
            在竞争存在时, 如果线程可以很快获得锁, 可以不在OS(操作系统)层挂起线程(性能损耗高),
            可以多几个空操作,这就叫自旋
            自旋锁jdk1.6 -XX:+UseSpinning    开启自旋锁, jdk 1.7中默认内置实现了自旋锁, 参数废弃.
            如果同步块很长,自选失败, 会降低系统性能
            如果同步块很短, 自旋成功, 节省挂起时的切换时间,提升性能
            
        偏向锁/轻量级锁/自旋锁不是java语言层锁优化方法.
        内置于JVM中的获取锁的优化方法和获取锁的步骤
                偏向锁可用会先尝试偏向锁
                轻量级锁可用会先尝试轻量级锁
                以上失败,会尝试自旋锁
                再失败, 尝试普通锁, 使用OS互斥量在操作系统曾挂起
            
Java语言层锁优化
        1. 减少锁持有时间(尽量再最小的区域内使用锁(synchronized)).
                就是只对需要同步块那部分代码使用同步块, 不必要代码不要放到同步块中
        
        2. 减小锁的粒度
                将需要锁定的大对象拆成小对象, 增加并行度, 较少锁竞争, 这样以来就可以增加偏向锁和轻量级锁的成功率
                该种实现例子就是ConcurrentHashMap
                HashMap实现同步可以通过Collections.synchronizedMap(map)获得一个同步SynchronizedMap,
                SynchronizedMap的方法进行了同步块实现
                ConcurrentHashMap的实现就是减小锁粒度
                        将内容分为若个segment片, 每个segment, 存储的k-v具体值.
                        在put操作时,先锁定某个segment块在进行put操作, 而不是锁定整个map.
                        减小锁粒度以后, ConcurrentHashMap就可以允许多个线程同时进入操作,只要多个线程
                        操作的是不同的segment.
        3.锁分离
                根据功能分离锁
                如读写锁ReadWriteLock,在读多写少情况时, 能提高性能.
                允许多读,但是只能一个写, 只要有一个线程获取读锁,其他读线程可读.写线程等待
                在有一个线程写时, 其他读线程和写线程等待.
                

                LinkedBlockingQueue  链表队列 头take,尾put, 头尾操作互不影响,

       可以一个线程put,一个take, 也是一种锁分离的应用

                
        4. 锁粗化
                通常情况下, 保证并发多线程有效, 要求每个线程持有锁的时间尽量少, 以便尽早释放锁, 其他线程得以获得.
                但是,如果对同一个锁频繁请求和释放过多也会导致资源消耗, 此时需要对锁进行粗化.
                比如: 下面的锁需要粗化, 放大同步块区域(synchronized时可重入锁)
                    public void method() {
                        synchronized(lock) {do ..}
                        synchronized(lock) {do ..}
                    }
                    public void method() {
                        for (x…) {
                            synchronized(lock) {do ..}
                        }
                    }
        
        5. 锁消除
                方法内不可能被多线程使用的变量, 但是出现了加锁, 此时编译器发现对象不可能被共享, 将会消除这些锁操作.
                例如: StringBuffer 是个线程安全的方法都有同步块, 但是此处不需要加锁, 编译器编译时会去掉锁操作
                    public String method(String s) {
                        StringBuffer sb = new StringBuffer();
                        sb.append(s);
                        return sb.toString();
                    }
                锁消除的JVM参数

                -XX:+DoEscapeAnalysis    逃逸分析, 锁对象是否逃逸(一个对象如果在方法内创建被使用,

           但是此后方法外也有使用, 被称为逃逸)

                -XX:+EliminateLocks        启用锁消除

   可以对上面方法循环2000000次计算耗时

                
        6.无锁
                锁是悲观操作, 悲观操作认为操作是多线程不安全的,存在竞争 ,所以使用锁
                无锁就是乐观操作, 乐观操作认为所有操作都是安全的不存在竞争关系
                
                无锁的实现方式

                        CAS(Compare And Swap) 比较交换, 进来就比较并交换值, 不等待, 成功继续执行,

          否则重试或者放弃.

                                        CAS有三个参数V(variable), E(expected), N(new),

     分别是    V需要操作的变量, E期望变量的值, N操作后的新值,

                                        交换过程为变量V的值与预期值E相等, 则将变量V赋值为N新值.
                                        if (v == e) v = n;
                                        CAS是cpu的一条指令, 能保证线程安全, 该种性能远远高于锁机制
                                        通过该方式应用在具体实现中, 判断是否多线程干扰, 如果有就重试.
                
                        java.util.concurrent.atomic包内存在很多原子操作的对象就是采用CAS如:AtomicInteger
                        

    原文作者:java锁
    原文地址: https://blog.csdn.net/fly_leopard/article/details/79210019
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞