CAS无锁算法:ReentrantLock,synchronized(JDK 1.6),悲观锁/乐观锁

> 悲观锁/乐观锁

 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。乐观锁的一种实现方式-CAS(Compare and Swap 比较并交换).CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

> Java1.6为Synchronized做了优化

尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。

 - jdk1.6以后 对synchronized锁做了哪些优化:
 1.适应自旋锁,自旋锁:为了减少线程状态改变带来的消耗 不停地执行当前线程 
 2.锁消除:不可能存在共享数据竞争的锁进行消除
 3.锁粗化:将连续的加锁 精简到只加一次锁
 4.轻量级锁:无竞争条件下 通过CAS消除同步互斥
 5.偏向锁:无竞争条件下 消除整个同步互斥,连CAS都不操作。

> CAS

  java.util.concurrent(简称JUC)包,ReentrantLock也是基于CAS的。
  CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。实现非阻塞同步的方案称为“无锁编程算法”( Non-blocking algorithm)。相对应的,独占锁是一种悲观锁,synchronized就是一种独占锁。
 
java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类,这些原子变量都调用了 sun.misc.Unsafe 类库里面的 CAS算法,用CPU指令来实现无锁自增,JDK源码:

public final int getAndIncrement() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
}  

public final boolean compareAndSet(int expect, int update) {  
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
}
  从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。Atomic操作的底层实现正是利用的CAS机制.
 无锁算法——CAS原理- https://blog.csdn.net/roy_70/article/details/69799845

  与锁相比,volatile变量是一和更轻量级的同步机制,因为在使用这些变量时不会发生上下文切换和线程调度等操作,但是volatile变量也存在一些局限:不能用于构建原子的复合操作,因此当一个变量依赖旧值时就不能使用volatile变量。
  Java中的原子操作( atomic operations),原子操作指的是在一步之内就完成而且不能被中断。原子操作在多线程环境中是线程安全的,无需考虑同步的问题。那么long型赋值不是原子操作呢?实时上java会分两步写入这个long变量,先写32位,再写后32位。这样就线程不安全了。如果改成下面的就线程安全了:private volatile long foo; 因为volatile内部已经做了synchronized.
  实现无锁(lock-free)的非阻塞算法有多种实现方法,其中 CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是一种 乐观锁 技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
   CAS看起来很爽,但是会导致“ABA问题”。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。
  CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

  优化锁实现的例子 :Java中的ConcurrentHashMap,设计巧妙,用桶粒度的锁和锁分离机制,避免了put和get中对整个map的锁定,尤其在get中,只对一个HashEntry做锁定操作,性能提升是显而易见的。
  Lock-free无锁的例子 :CAS(Compare-And-Swap)的利用和LMAX的disruptor 无锁消息队列数据结构等。例如ConcurrentLinkedQueue。

扫描二维码关注公众号,回复: 4007183 查看本文章

 轻量级锁和偏向锁都是在没有竞争的情况下出现,一旦出现竞争就会升级为重量级锁。对于synchronized,锁的升级情况可能是 偏向锁—>轻量锁—>自适应自旋锁—>重量锁。

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
 1.A线程写volatile变量,随后B线程读这个volatile变量。
 2.A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
 3.A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
 4.A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

  AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。

关于Java锁机制- https://yq.aliyun.com/articles/607025?utm_content=m_1000005240#

猜你喜欢

转载自blog.csdn.net/ShareUs/article/details/83926324