• 周五. 10月 7th, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

java并发包 Atomic

admin

11月 28, 2021

CAS机制

  除了synchronized之外,java还提供了一些并发包。比如现在这段代码,肯定会有并发问题,我们当然可以通过重磅的 synchronized 锁来解决多线程并发问题,但是这样就有点杀鸡用牛刀了。我们可以用Atomic原子类来解决这个问题。

import java.util.concurrent.atomic.AtomicInteger;
public class Demo {
    // 初始值0
    private static int num = 0;
    public static void inCreate(){
        //自增
        num++;
    }
//    Atomic 解决并发问题
//    // 初始值0
//    private static AtomicInteger num = new AtomicInteger(0);
//    public synchronized static void inCreate(){
//        // 自增
//        num.incrementAndGet();
//    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        Thread.sleep(2000);// 等待两秒,让上面10个线程先执行完
        System.out.println(num);// 结果为1000才是对的
    }
}

  Atomic 原子类不是传统意义的锁机制,它是无锁化的 CAS 机制,通过 CAS 机制保证多线程修改个数值的安全性,CAS的全称是:Compare and Set,也就是先较再设置的意思。

  

Atomic源码

  我们在操作 atomic 的时候,实际上就是对 value 的修改,它是被 volatile 修饰的,也就是说变更之后马上就可以被其他线程感知到。

  我们调用 incrementAndGet 自增方法,就是通过 Unsafe 类来做的,它负责执行 CAS操作。它是jdk内部的一个类,我们在自己的系统中使用 Unsafe.getUnsafe() 是会报错的,因为类加载器是不同的。

先分析下参数,看看它整个的调用过程:
  var1:就是我们传入的this,就是 AtomicInteger 这个类对象。
  var2:就是 valueOffest,也就是我们的value的偏移量。
  var4:增加的值,1。
  var5:内存中旧的值。
  首先通过 this.getIntVolatile(var1, var2); 根据 AtomicInteger 的value偏移量,获取到了内存中旧的值 var5 ,就是我们当时设置的0。
  然后通过 while 无限循环去执行 this.compareAndSwapInt(AtomicInteger, valueOffest, 0, 0 + 1)。这个过程就是 CAS 操作,它是被 native 修饰的,底层就是 C 发送的 cpu 指令,通过一个线程对某块小内存中的数据进行修改。
  最后就把旧的值 var5 返回来了,然后 return 0+1,我们就看到了新的值 1。

CAS优化

  刚才我们模拟CAS执行过程知道,要是获取到的值和内存不一致,那么它会一直无线循环;如果是大量线程同时涌入,必然会导致大量线程空循环,性能和效率都不是特别好。所以java8使用LongAdder使用分段的方式进行了优化。在longadder中,一开始所有线程都是对base进行操作。如果线程较多,就会实行分段CAS机制,也就是内部会搞一个cell数组,每个数组是一个数值分段,让每一个cell去处理一些请求;如果某一个cell执行cas失败了,则会自动去找另一个cell进行cas操作;最后将所有cell分段数值加起来返回给你。

    // LongAdder 解决并发问题
    // 初始值0
    private static LongAdder num = new LongAdder();
    public synchronized static void inCreate(){
        // 自增
        num.add(1);
    }

  

发表回复

您的电子邮箱地址不会被公开。