十二、Java高级特性(CAS基本原理)

2021-05-31  本文已影响0人  大虾啊啊啊

一、什么是原子操作?如何实现原子操作?

如果有两个操作A和B,分别有两个不同的线程执行。从A所在线程来看,执行B的线程执行B的时候要么将B执行完毕,要么完全不执行B,那么A和B对彼此来说是原子的。

1、实现原子操作可以用锁

锁机制基本满足了需求是没有问题的。但是有时候我们需要更加有效,更加灵活,synchronized关键字是基于阻塞的锁机制。也就是说当有一个线程拥有锁的时候,访问同一个资源的其他线程必须处于等待状态。直到该线程释放锁。
这就会有一个问题:假如被阻塞的线程优先级很高怎么办?其次就是获得锁的线程一直不释放锁怎么办?还有就是如果有大量的线程来竞争资源,那么CPU要花大量的时间和资源来处理这些竞争。同时还可能出现死锁的情况。锁是一种比较粗糙,粒度比较大的机制,对于一些像计数器这样的需求,就显得比较笨重。

2、实现原子操作还可以使用现代CPU支持的CAS指令

每一个CAS包含了三个运算符:
(1)内存地址V
(2)期望值A
(3)新值B

CAS举例

现在我们有3个线程要对一个共享变量i实现+1的操作,理论上三个线程+1完最后的结果是3。我们可以使用synchronized锁的机制来实现,同时也可以使用更加轻量的CAS指令实现。
下面我们介绍其原理:如下图


image.png

(1)三个线程都是都可以进行i++,不需要阻塞等待,但是+1的时候并没有真正的写入内存。每个线程i++完之后,各自的i都是1。
(2)进入图中的compare swap操作,比较和交换,这个操作属于原子操作。即要么全部执行,要么不执行,因此在这每次只有一个线程执行或者不执行。假如三个线程分别为A 、B、C。A线程进入原子操作比较和交换的时候,i的内存地址是V,期望值是i= 0,新值是i= 1。如果比较的时候i确实等于0,那么进行交换,将i = 1,写入内存,A线程原子操作完毕。B线程也一样,i的内存地址是V,期望值是i= 0,新值是i= 1。此时B线程进行原子操作比较交换的时候发现i= 1,自己的期望值是0。他们俩不相等,因此从新来一次,将期望值置为1,新值i= 1+1 =2。在进行原子操作比较和交换,这个时候如果C线程没有经过原子操作修改过i的值,那么此时B线程原子操作比较交换的时候,内存地址V中i的值是1,B的期望值是1,新值是2。内存V中地址的值和期望值一样,因此交换。把新值i= 2写入内存地址V中。当C线程进来的是也是一样的道理。在原子操作的时候,当原子操作比较交换的时候,内存V中的值和期望值不一样,那么再来一次,将期望值修改。这个操作称为自旋。

二、实现原子操作的三大问题

1、ABA问题

因为CAS需要在操作值的时候,检查内存V中的值和自己的期望值是否相等,如果相等,则将新值写入内存。但是并不知道内存V中的值中间是否发生了变化,例如当一个线程的期望值是A,但是内存地址V中的值,经过其他线程从A改成B,从B又改成A。当该线程进行CAS原子操作的时候,发现内存地址V中的值是A,和自己的期望值相等,那么就直接将新值写入到内存。然而它并不知道其实值已经经过多次的变化。
那么如果对于需要知道A是否被更改过的要求,就会产生问题。解决这一问题,可以加一个版本号。也就是每次更改内存V中的值的时候,把版本号+1。通过拿到版本号就可以知道是否被更改过。

2、循环时间过长,开销大。

自旋CAS如果时间太长,会给CPU带来大的执行开销。

3、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作

三、Jdk中相关原子操作类的使用

四、AtomicInteger的使用举例

package com.it.test.thread.consumer_product.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 基本类型的原子操作
 */
public class CasTest {
    static AtomicInteger atomicInteger  = new AtomicInteger(10);
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println(atomicInteger.getAndIncrement());//i++
            }
        }.start();
        new Thread(){
            @Override
            public void run() {

                System.out.println(atomicInteger.incrementAndGet());//++i
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                System.out.println( atomicInteger.getAndAdd(24));//i++
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                System.out.println(atomicInteger.addAndGet(30));//++i
            }
        }.start();




    }
}


10
12
12
66
上一篇 下一篇

猜你喜欢

热点阅读