美文网首页
synchronized和juc比较

synchronized和juc比较

作者: _火山_ | 来源:发表于2021-02-26 15:47 被阅读0次

    为什么有了synchronized之后还需要juc的锁呢?

    1、synchronized是非公平锁,无法用作公平锁,某些需要公平锁的场景用不了。
    2、所以就出现了juc:一系列的锁,除了使用juc原生的锁之外,还可以基于juc提供的锁接口自定义锁,可扩展性好。

    CAS流程:

    线程读取变量的值到工作内存中,比较该值与线程预期的值是否一致,如果是,则将该变量值改为目标值;
    否则,循环重新读取变量值到工作内存中,比较该值与预期的是否一致,如果是,则将该变量值改为目标值;
    否则,循环上述步骤,直到修改成功;
    如果一直修改不成功呢,就会一直执行这个循环,占用着cpu资源,没法做有意义的事情。

    CAS可能会导致的问题:
    1、ABA问题:
    1)ABA问题是啥?
    一个线程x把变量值从A改为了B,后来又来了一个线程y又把变量的值由B改回了A,但是在我这个线程z看来这个变量的值是A,跟我预期的值是一致的,所以就会将A改为了线程z的目标值。
    虽然这个变量值最终是A,但是它却是经过了由A->B->A这样的过程,所以这个A值其实不是真正预期的那个没有被修改的了。
    对于值相同,但是其实是被修改过的,在某些场景上其实是不允许出现的,比如金融行业上,私下挪用了某人的钱,金额减少,在别人发现前把钱还回去,金额又恢复原数目,但是这笔钱其实是已经被动过的了,而这个是不允许的,私下挪用是犯法的。
    2)怎么解决这个ABA问题?
    可以对原子类的值加版本号来解决,假如该原子类有个初始值A,初始版本号为1,那么后续的每一次修改都要带上版本号,不仅比较原子类的值本身是否满足预期,还要比较该值的版本号是否满足预期,如果都满足,那么就可以修改原子类变量的值,并且版本号要递增的,如从A->B,则应该是1A->2B,即原子类的值变化了,且值的版本号也增大了;
    每一次值的修改,版本号都会增大,那么当发生A->B->A这样的问题时,带上版本号后,就会变为1A->2B->3A,每一次修改时都比较变量值和版本,如果其中任何一个不同,那么最终都不能修改,这样的话,当A经过改为B再重新改为A后,它的版本号变了,就可能与我预期的[A,1]不一致了,最终就不会执行修改操作。

    在jdk1.5及之后,出现了AtomicStampedReference这个类,可以用来代理原子类的使用,主要是解决了原子类cas会出现ABA的问题,其解决的原理也是通过版本号实现的。
    AtomicStampedReference本质上也是原子类,但是它与其他的原子类的区别是:它封装了一个对象引用,并且为这个对象引用带上一个版本号:[reference, integer],所以其本质上是一个引用/版本的pair对。

    版本号是用int类型来记录,而封装的引用类型则是一个泛型。

    public class AtomicStampedReference<V> {
    
        private static class Pair<T> {
            final T reference;
            final int stamp;
            private Pair(T reference, int stamp) {
                this.reference = reference;
                this.stamp = stamp;
            }
            static <T> Pair<T> of(T reference, int stamp) {
                return new Pair<T>(reference, stamp);
            }
        }
    
        private volatile Pair<V> pair;
    
        ...
    }
    

    所以为了解决CAS的ABA问题,在某些要求严格的场景下,可以使用AtomicStampedReference,而不是直接使用AtomicInteger、AtomicLong等这些。

    但是如果场景要求不严格,比如A->B->A后,还可以认为A是同一个的话,那么是可以直接选择使用AtomicInteger、AtomicLong等,而没必要使用AtomicStampedReference。

    2、原子性问题:
    必须保证原子性:"比较/替换"这两个操作要保证原子性。

    原子类为什么可以保证原子性?
    原子类里面其实是调用了Unsafe类的compareAndSwap方法,它是一个native方法。
    最终C++底层的实现是是:lock,comxchg
    先lock,锁总线(其实还会有一个升级过程)
    执行了lock之后,才能保证原子性,
    再comxchg,完成操作系统级别的CAS。
    而如果存粹仅是执行comxchg,还是无法保证原子性的。

    CAS效率比synchronized的高?

    不一定。
    从cpu消耗方面和与操作系统交互方面比较选择哪个好一点?

    从任务的耗时层面比较效率:
    如果临界区的逻辑非常耗时,那么cas方式在锁被释放前一直在空转,cpu资源没有用于做真正有意义的事情,这样cpu资源就会很浪费,所以对于比较耗时的任务的话,不建议使用cas方式,即不建议使用juc下的锁,像Reentranlock等,而是建议选择synchronized锁。

    从与操作系统交互层面来比较效率:
    因为当synchronized升级为重量级锁之后,每次获取锁是要通过操作系统决定的,即每个线程获取锁都要涉及到上下文切换,从用户态切换为内核态,又从内核态再切换为用户态,这个过程比较耗时,所以当涉及到获取锁需要访问操作系统时,需要再权衡下锁住的临界区的逻辑是否非常耗时,如果不是非常耗时的逻辑,即可以很快就执行完的,这时候其实是可以选择CAS方式的,因为此时临界区的逻辑耗时短,线程通过CAS空转消耗的CPU资源也就少了,所以可以选择CAS;
    但是如果临界区依然是非常耗时的话,那么为了避免CPU资源的浪费还是建议选择synchronized。

    相关文章

      网友评论

          本文标题:synchronized和juc比较

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