并发及CAS

admin
2022-04-19 / 0 评论 / 249 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年04月20日,已超过729天没有更新,若内容或图片失效,请留言反馈。

计算机架构

CPU多级缓存

为什么需要CPU cache

CPU的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源。所以cache的出现,是为了缓解CPU和内存之间速度的不匹配问题(结构:cpu->cache->memory)。

CPU cache有什么意义

1)时间局部性:如果某个数据被访问,那么在不久的将来它很可能被再次访问
2)空间局部性:如果某个数据被访问,那么与它相邻的数据很快也可能被访问;

CPU 多级缓存一致性(MESI)

CPU缓存4种状态

M:被修改状态,指该缓存行只被缓存在该CPU的缓存中,并且是被修改过的。因此与主存中的数据是不一致的,该缓存行中的内存需要再未来的某个时间点写会主存。这个时间点是允许其它CPU读取主存中相应的内存之前,当这里面的值被写会主存之后该缓存行的状态会变成E独享状态。

E:独享状态,指缓存行只被缓存在该CPU的缓存中,它是未被修改过的,是和主存的数据一致的,这个状态可以在任何时刻,当有其它CPU读取该内存时变成S共享状态。当CPU修改该缓存行内容时,状态变成M状态。

S:共享状态,意味该缓存行可能被多个CPU缓存,并且各CPU缓存中的数据与主存中的数据一致,当有一个CPU修改该缓存行时,其它CPU中该缓存行,是可以被作废的,变成I无效状态。

I:无效状态,代表缓存无效,可能有其它CPU修改了该缓存行。

引起CPU缓存状态变化的4种操作

local read:读本地缓存中的数据

local write:将数据写到本地的缓存里面

remote read:将主内中的数据读取到本地缓存中

remote write:将本地缓存中数据写到主存里面

MESI协议:CPU缓存数据有4种状态,引起CPU缓存转变的操作也有4种,因此本质就是要理解这16种操作状态的关系,可以通过下图表示:

重点理解

1、CPU缓存是为了减少CPU读写共享主存的次数。

2、除了I状态,其它三种状态都是可以被读取的。

3、只有再M和E状态才能写。

4、当为S状态时,必须将该缓存行变为无效状态,一般通过广播方式完成,此时不允许不同的CPU同时修改该缓存行,即使修改该缓存行不同位置的数据也是不允许的,这里主要解决缓存一致性的问题

5、只有M和E状态可以写,如果是S状态需要变为I状态。

6、一个处于M装的缓存行,需要时刻监听所有试图读该缓存行,相对有主存的操作,这种操作必须将缓存行写会到主存,并将状态变为S状态之前,被延迟执行。

7、S状态的缓存行,监听所有试图将该缓存行无效或独享该缓存行的请求,并将缓存行变为无效。

8、处于E状态的缓存行,需要监听其它读该缓存行的操作,一旦有该缓存行的操作,那么该缓存行需要变为S状态。因此对于M和E状态的数据总是精确的,和缓存行的状态是真正一致的。S状态可能是非一致的。如果一个缓存将处于S状态的数据作废了,另一个缓存实际上可能已经独享了该缓存行,但是该缓存不会把缓存行升迁为E状态,因为其它不会广播该作废的通知,同样由于缓存没有缓存该COPY的数量,因此也无法确定自己是否独享了该缓存行。E状态更像一种投机的优化,因为当一个CPU想修改一个处于S共享状态的缓存行,总线事务需要将所有copy该缓存行缓存的值变成I状态才行。修改E状态的CPU缓存不需要总线事务。


JAVA内存模型抽象结构图

JAVA内存模型8个操作

8种同步操作

  1. lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态
  2. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
  6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量
  7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
  8. write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中

同步规则

  1. 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行
  2. 不允许read和load、store和write操作之一单独出现
  3. 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中
  4. 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中
  5. 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
  6. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  7. 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  8. 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量
  9. 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)

compareAndSetInt

举例一

package com.yanxizhu.demo.concurrency.atomic;

import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description: 线程安全
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/19 20:05
 * @version: 1.0
 */
@Slf4j
@ThreadSafety
public class DemoSemaphoreAndCountDownLatchAndAtomic {

    //用户数量
    private static final int clientsTotal = 5000;
    //并发数量
    private static final int concurrencyTotal = 200;
    //累加总和
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量
        final Semaphore semaphore = new Semaphore(clientsTotal);
        //闭锁
        final CountDownLatch countDownLatch = new CountDownLatch(concurrencyTotal);

        for (int i = 0; i < clientsTotal; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (InterruptedException e) {
                    log.error("出现错误:【{}】", e.getMessage());
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("累加结果为count=【{}】",count.get());
    }

    /**
     * 累加
     */
    private  static void add(){
        //说明:incrementAndGet、getAndIncrement类似,i++与++i
        count.incrementAndGet();
        count.getAndIncrement();
    }
}

源码解析

count.incrementAndGet();
public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

weakCompareAndSetInt(o, offset, v, v + delta)

@HotSpotIntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset,
                                          int expected,
                                          int x) {
    return compareAndSetInt(o, offset, expected, x);
}

compareAndSetInt(o, offset, expected, x)

@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                             int expected,
                                             int x);

native

native标识,说明是Java底层操作方法。

重点说明

@HotSpotIntrinsicCandidate
/**
* o:      当前对象,上面的count
* offset: 当前值  (例如2+1),offset=2
* delta:         (例如2+1),delta=1
**/
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        //v:底层获取的值,如果没有其它线程修改,理论应该是2
        v = getIntVolatile(o, offset);
        //重点:
        //   1)、当没有其它线程修改底层值时:
        //       o这个对象,当前值offset=2与底层值v=2相等,则把底层值更新为(底层值v=2 + 增加值delta=1 = 最后就等于3)
        //
        //   2)、如果此有其它线程修改了底层值v(例如修改为v=4):
        //       此时低层值v=4与当前值offset=2不相等,则重新从count对象取出当前值offset=4,
        //       再判断当前值offset=4与底层值v=4相等,然后再讲底层值修改为(低层值v=4 + 增加值delta= 最后就登陆5),
        //       如果一直有其它线程修改底层值v,导致当前值offset与底层值v一直不相等,则一直循环上面的操作。
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

CAS就是compareAndSet的缩写。底层值其实就是计算机的主内存,当前值就是JMM模型中工作内存值。

特别说明

JDK1.8后新增了LongAdder。

AtomicLong:是基于 CAS 方式自旋更新的;

LongAdder: 是把 value 分成若干cell,并发量低的时候,直接 CAS 更新值,成功即结束。并发量高的情况,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋 CAS 更新 cell值。取值的时候,调用 sum() 方法进行每个cell累加。

AtomicLong: 包含有原子性的读、写结合的api;

LongAdder :没有原子性的读、写结合的api,能保证结果最终一致性。

低并发:低并发场景AtomicLong 和 LongAdder 性能相似。

高并发:高并发场景 LongAdder 性能优于 AtomicLong。

3

评论 (0)

取消