美文网首页
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比较

    为什么有了synchronized之后还需要juc的锁呢? 1、synchronized是非公平锁,无法用作公平锁...

  • Java面试题

    简述synchronized?Object;Monitor机制;简述happen-before规则;JUC和Obj...

  • 同步器AbstractQueuedSynchronizer浅析

    Java中的锁主要有:synchronized锁和JUC(java.util.concurrent)locks包中...

  • Lock和Synchronized区别底层原理分析

    Lock是java5后出现的,是在juc包。synchronized实现原理其实就是通过monitorenter和...

  • 理解J.U.C中的ReentrantLock

    JUC(java.util.concurrent工具包的简称) Lock(synchronized) Reentr...

  • java并发锁相关知识点梳理

    导读:记录下java中提供锁的两种方式:synchronized和Lock(juc里面提供的锁) 一、问题的产生和...

  • volatile和synchronized比较

    volatile主要作用使变量在多个线程间可见,强制从公共内存中取到值 关键字volatile是线程同步的轻量级实...

  • Lock和Synchronized比较

    Lock接口和Synchronized关键字都提供了多线程中锁的功能,用来控制多个线程访问共享资源的方式,Sync...

  • JUC中的锁(一)概述

    Java中的锁,可以分为Synchronized”同步锁”和”JUC包中的锁”。同步锁之前说过很多了,可以看之前的...

  • JUC包

    JUC包原因 synchronized性能不高;(JDK1.6及以前版本) wait/notify太原始,难用;例...

网友评论

      本文标题:synchronized和juc比较

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