Atomic

Java中高级核心知识全面解析——Atomic(什么是Atomic原子类、基本类型原子类、数组类型原子类、引用类型原子类?) 一、Atomic 原子类介绍 Atomic翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 并发包java.util.concurrent的原子类都存放在java.util.concurrent.atomic下,如下图所示。 根据操作的数据类型,可以将JUC包中的原子类分为4 阅读详情

所谓 Atomic,翻译过来就是原子。原子被认为是操作中最小的单位,一段代码如果是原子的,则表示这段代码在执行过程中,要么执行成功,要么执行失败。原子操作一般都是底层通过 CPU 的指令来实现。而 atomic 包下的这些类,则可以让我们在多线程环境下,通过一种无锁的原子操作来实现线程安全。

atomic 包下的类基本上都是借助 Unsafe 类,通过 CAS 操作来封装实现的。Unsafe 这个类不属于 Java 标准,或者说这个类是 Java 预留的一个后门类,JDK 中,有关提升性能的 concurrent 或者 NIO 等操作,大部分都是借助于这个类来封装操作的。
Java 是种编译型语言,不像 C 语言能支持操作内存,正常情况下都是由 JVM 进行内存的创建回收等操作,但这个类提供了一些直接操作内存相关的底层操作,使得我们也可以手动操作内存,但从类的名字就可以看出,这个类不是安全的,官方也是不建议我们使用的。

原子操作类介绍

在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量i=1,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的(关于synchronized可以看这篇文章)。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。

CAS原理

CAS 包含 3 个参数 CAS(V,E,N). V 表示要更新的变量, E 表示预期值, N表示新值.

仅当V值等于E值时, 才会将V的值设为N, 如果V值和E值不同, 则说明已经有其他线程做了更新, 则当前线程什么都不做. 最后, CAS返回当前V的真实值. CAS操作是抱着乐观的态度进行的, 它总是认为自己可以成功完成操作.

当多个线程同时使用CAS操作一个变量时, 只有一个会胜出, 并成功更新, 其余均会失败.失败的线程不会被挂起,仅是被告知失败, 并且允许再次尝试, 当然也允许失败的线程放弃操作.基于这样的原理, CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰, 并进行恰当的处理.

在 JDK8 的 atomic 包下,大概有 16 个类,按照原子的更新方式,大概可以分为 4 类:原子更新普通类型,原子更新数组,原子更新引用,原子更新字段。

CAS操作

使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。
CAS的实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。

Synchronized VS CAS

元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的间线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。

CAS的问题

  1. ABA问题
    因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。

  2. 自旋时间过长

使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。如果JVM能支持处理器提供的pause指令,那么在效率上会有一定的提升。

原子更新普通类型

atomic 包下提供了三种基本类型的原子更新,分别是 AtomicBoolean,AtomicInteger,AtomicLong,这几个原子类对应于基础类型的布尔,整形,长整形,至于 Java 中其他的基本类型,如 float 等,如果需要,可以参考这几个类的源码自行实现。

AtomicBoolean

主要接口

public final boolean get();
public final boolean compareAndSet(boolean expect, boolean update);
public boolean weakCompareAndSet(boolean expect, boolean update);
public final void set(boolean newValue);
public final void lazySet(boolean newValue);
public final boolean getAndSet(boolean newValue);

这里面的操作都很正常,主要都是用到了 CAS。这个类中的方法不多,基本上上面都介绍了,而内部的计算则是先将布尔转换为数字0/1,然后再进行后续计算。

AtomicLong

主要接口

public final long get();
public final void set(long newValue);
public final void lazySet(long newValue);
public final long getAndSet(long newValue);
public final boolean compareAndSet(long expect, long update);
public final boolean weakCompareAndSet(long expect, long update);
public final long getAndIncrement();
public final long getAndDecrement();
public final long getAndAdd(long delta);
public final long incrementAndGet();
public final long decrementAndGet();
public final long addAndGet(long delta);
public final long getAndUpdate(LongUnaryOperator updateFunction);
public final long updateAndGet(LongUnaryOperator updateFunction);
 

AtomicInteger

主要接口

// 取得当前值
public final int get();
// 设置当前值
public final void set(int newValue);
// 设置新值,并返回旧值
public final int getAndSet(int newValue);
// 如果当前值为expect,则设置为u
public final boolean compareAndSet(int expect, int u);
// 当前值加1,返回旧值
public final int getAndIncrement();
// 当前值减1,返回旧值
public final int getAndDecrement();
// 当前值增加delta,返回旧值
public final int getAndAdd(int delta);
// 当前值加1,返回新值
public final int incrementAndGet();
// 当前值减1,返回新值
public final int decrementAndGet();
// 当前值增加delta,返回新值
public final int addAndGet(int delta);

代码实现

 // 封装了一个int对其加减
 private volatile int value;
 .......
 public final boolean compareAndSet(int expect, int update) {
 // 通过unsafe 基于CPU的CAS指令来实现, 可以认为无阻塞.
 return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
 }
 .......
 public final int getAndIncrement() {
 for (;;) {
 // 当前值
 int current = get();
 // 预期值
 int next = current + 1;
 if (compareAndSet(current, next)) {
 // 如果加成功了, 则返回当前值
 return current;
 }
 // 如果加失败了, 说明其他线程已经修改了数据, 与期望不相符,
 // 则继续无限循环, 直到成功. 这种乐观锁, 理论上只要等两三个时钟周期就可以设值成功
 // 相比于直接通过synchronized独占锁的方式操作int, 要大大节约等待时间.
 }
 }

代码案例 

 

AtomicInteger atomicInteger = new AtomicInteger(1);
System.out.println(atomicInteger.incrementAndGet()); // 2
System.out.println(atomicInteger.getAndIncrement()); // 2
System.out.println(atomicInteger.getAndAccumulate(2, (i, j) -> i + j)); // 3
System.out.println(atomicInteger.get()); // 5
System.out.println(atomicInteger.addAndGet(5)); 

原子更新数组

tomic 包下提供了三种数组相关类型的原子更新,分别是 AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,对应于整型,长整形,引用类型,要说明的一点是,这里说的更新是指更新数组中的某一个元素的操作。

由于方法和更新基本类型方法相同,这里只简单看下 AtomicIntegerArray 这个类的几个方法,其他的方法类似。

AtomicIntegerArray

主要接口

// 获得数组第i个下标的元素
public final int get(int i);
// 获得数组的长度
public final int length();
// 将数组第i个下标设置为newValue,并返回旧的值
public final int getAndSet(int i, int newValue);
// 进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
public final boolean compareAndSet(int i, int expect, int update);
// 将第i个下标的元素加1
public final int getAndIncrement(int i);
// 将第i个下标的元素减1
public final int getAndDecrement(int i);
// 将第i个下标的元素增加delta(delta可以是负数)
public final int getAndAdd(int i, int delta);

源码实现

 // 数组本身基地址
 private static final int base = unsafe.arrayBaseOffset(int[].class);
 
 // 封装了一个数组
 private final int[] array;
 
 static {
 // 数组中对象的宽度, int类型, 4个字节, scale = 4;
 int scale = unsafe.arrayIndexScale(int[].class);
 if ((scale & (scale - 1)) != 0)
 throw new Error("data type scale not a power of two");
 // 前导0 : 一个数字转为二进制后, 他前面0的个数
 // 对于4来讲, 他就是00000000 00000000 00000000 00000100, 他的前导0 就是29
 // 所以shift = 2
 shift = 31 - Integer.numberOfLeadingZeros(scale);
 }
 
 // 获取第i个元素
 public final int get(int i) {
 return getRaw(checkedByteOffset(i));
 }
 
 // 第i个元素, 在数组中的偏移量是多少
 private long checkedByteOffset(int i) {
 if (i < 0 || i >= array.length)
 throw new IndexOutOfBoundsException("index " + i);
 
 return byteOffset(i);
 }
 
 // base : 数组基地址, i << shift, 其实就是i * 4, 因为这边是int array.
 private static long byteOffset(int i) {
 // i * 4 + base
 return ((long) i << shift) + base;
 }
 
 // 根据偏移量从数组中获取数据
 private int getRaw(long offset) {
 return unsafe.getIntVolatile(array, offset);
 }

测试代码

AtomicIntegerArray array = new AtomicIntegerArray(5);
array.set(0, 1); // 设置数组第一个值为1
System.out.println(array.getAndDecrement(0)); // 1
System.out.println(array.addAndGet(0, 5)); // 5

原子更新引用

更新引用类型的原子类包含了AtomicReference(更新引用类型),AtomicReferenceFieldUpdater(抽象类,更新引用类型里的字段),AtomicMarkableReference(更新带有标记的引用类型)这三个类,这几个类能同时更新多个变量。

AtomicReference

与 AtomicInteger 类似, 只是里面封装了一个对象, 而不是 int, 对引用进行修改。

主要接口

public final V get();
 
public final void set(V newValue);
 
public final boolean compareAndSet(V expect, V update);
 
public final V getAndSet(V newValue);
public class AtomicDemo {

    private static AtomicReference<User> reference = new AtomicReference<>();

    public static void main(String[] args) {
        User user1 = new User("a", 1);
        reference.set(user1);
        User user2 = new User("b",2);
        User user = reference.getAndSet(user2);
        System.out.println(user);
        System.out.println(reference.get());
    }

    static class User {
        private String userName;
        private int age;

        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:
User{userName='a', age=1}
User{userName='b', age=2}

原子更新字段

如果更新的时候只更新对象中的某一个字段,则可以使用 atomic 包提供的更新字段类型:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater 和 AtomicStampedReference,前两个顾名思义,就是更新 int 和 long 类型,最后一个是更新引用类型,该类提供了版本号,用于解决通过 CAS 进行原子更新过程中,可能出现的 ABA 问题。
前面这两个类和上面介绍的 AtomicReferenceFieldUpdater 有些相似,都是抽象类,都需要通过 newUpdater 方法进行实例化,并且对字段的要求也是一样的。

AtomicStampedReference

ABA问题

线程一准备用 CAS 将变量的值由 A 替换为 B, 在此之前线程二将变量的值由 A 替换为 C, 线程三又将 C 替换为A, 然后线程一执行 CAS 时发现变量的值仍然为 A, 所以线程一 CAS 成功.

主要接口

// 比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
// 获得当前对象引用
public V getReference()
// 获得当前时间戳
public int getStamp()
// 设置当前对象引用和时间戳
public void set(V newReference, int newStamp)

源码分析

 // 内部封装了一个Pair对象, 每次对对象操作的时候, stamp + 1
 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操作的时候, 会对比stamp的值
 public boolean compareAndSet(V expectedReference,
 V newReference,
 int expectedStamp,
 int newStamp) {
 Pair<V> current = pair;
 return
 expectedReference == current.reference &&
 expectedStamp == current.stamp &&
 ((newReference == current.reference &&
 newStamp == current.stamp) ||
 casPair(current, Pair.of(newReference, newStamp)));
 }

代码测试

要求:后台使用多个线程对用户充值, 要求只能充值一次.

public class AtomicStampedReferenceDemo {
 static AtomicStampedReference<Integer> money=new AtomicStampedReference<Integer>(19,0);
 public staticvoid main(String[] args) {
 //模拟多个线程同时更新后台数据库,为用户充值
 for(int i = 0 ; i < 3 ; i++) {
 final int timestamp=money.getStamp();
 newThread() { 
 public void run() { 
 while(true){
 while(true){
 Integerm=money.getReference();
 if(m<20){
 if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){
                 System.out.println("余额小于20元,充值成功,余额:"+money.getReference()+"元");
 break;
 }
 }else{
 //System.out.println("余额大于20元,无需充值");
 break ;
 }
 }
 }
 } 
 }.start();
 }
 
 //用户消费线程,模拟消费行为
 new Thread() { 
 publicvoid run() { 
 for(int i=0;i<100;i++){
 while(true){
 int timestamp=money.getStamp();
 Integer m=money.getReference();
 if(m>10){
 System.out.println("大于10元");
   if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
        System.out.println("成功消费10元,余额:"+money.getReference());
 break;
 }
 }else{
 System.out.println("没有足够的金额");
 break;
 }
 }
 try {Thread.sleep(100);} catch (InterruptedException e) {}
 }
 } 
 }.start(); 
 }
 }

AtomicIntegerFieldUpdater

能够让普通变量也能够进行原子操作。

主要接口

public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
 String fieldName);
 
public int incrementAndGet(T obj);
  • Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。比如如果score申明为private,就是不可行的。
  • 为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得申明一下就行。
  • 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe.objectFieldOffset()不支持静态变量)。

测试

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 
public class AtomicIntegerFieldUpdaterDemo {
 public static class Candidate {
 int id;
 // 如果直接把int改成atomicinteger, 可能对代码破坏比较大
 // 因此使用AtomicIntegerFieldUpdater对score进行封装
 volatile int score;
 }
 
 // 通过反射实现
 public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
 // 检查Updater是否工作正确, allScore的结果应该跟score一致
 public static AtomicInteger allScore = new AtomicInteger(0);
 
 public static void main(String[] args) throws InterruptedException {
 final Candidate stu = new Candidate();
 Thread[] t = new Thread[10000];
 for (int i = 0; i < 10000; i++) {
 t[i] = new Thread() {
 public void run() {
 if (Math.random() > 0.4) {
 scoreUpdater.incrementAndGet(stu);
 allScore.incrementAndGet();
 }
 }
 };
 t[i].start();
 }
 for (int i = 0; i < 10000; i++) {
 t[i].join();
 }
 
 System.out.println("score=" + stu.score);
 System.out.println("allScore=" + allScore);
 }
}
 

JDK8之后引入的类型

在JDK8之前,针对原子操作,我们基本上可以通过上面提供的这些类来完成我们的多线程下的原子操作,不过在并发高的情况下,上面这些单一的 CAS + 自旋操作的性能将会是一个问题,所以上述这些类一般用于低并发操作。
而针对这个问题,JDK8又引入了下面几个类:DoubleAdder,LongAdder,DoubleAccumulator,LongAccumulator,这些类是对AtomicLong这些类的改进与增强,这些类都继承自Striped64这个类。

 

 

CAS线程安全

说了半天,我们要回归到最原始的问题了:这样怎么实现线程安全呢?请大家自己先考虑一下这个问题,其实我们在语言层面是没有做任何同步的操作的,大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!

 

为什么需要AtomicInteger原子操作类?

对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。测试代码如下:

public class AtomicIntegerTest {
 
    private static final int THREADS_CONUT = 20;
    public static int count = 0;
 
    public static void increase() {
        count++;
    }
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_CONUT];
        for (int i = 0; i < THREADS_CONUT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

这里运行了20个线程,每个线程对count变量进行1000此自增操作,如果上面这段代码能够正常并发的话,最后的结果应该是20000才对,但实际结果却发现每次运行的结果都不相同,都是一个小于20000的数字。这是为什么呢?

要是换成volatile修饰count变量呢?

顺带说下volatile关键字很重要的两个特性:

1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—"先行发生原则");

2、禁止指令的重排序优化;

那么换成volatile修饰count变量后,会有什么效果呢? 试一试:

public class AtomicIntegerTest {
 
    private static final int THREADS_CONUT = 20;
    public static volatile int count = 0;
 
    public static void increase() {
        count++;
    }
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_CONUT];
        for (int i = 0; i < THREADS_CONUT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

结果似乎又失望了,测试结果和上面的一致,每次都是输出小于20000的数字。这又是为什么么? 上面的论据是正确的,也就是上面标红的内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。

用了AtomicInteger类后会变成什么样子呢?

把上面的代码改造成AtomicInteger原子类型,先看看效果

import java.util.concurrent.atomic.AtomicInteger;
 
public class AtomicIntegerTest {
 
    private static final int THREADS_CONUT = 20;
    public static AtomicInteger count = new AtomicInteger(0);
 
    public static void increase() {
        count.incrementAndGet();
    }
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_CONUT];
        for (int i = 0; i < THREADS_CONUT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。

非阻塞同步

同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。

我们知道,阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做非阻塞同步呢?在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。在硬件指令集的发展驱动下,使得 "操作和冲突检测" 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。《深入理解Java虚拟机第二版.周志明》第十三章中这样描述关于CAS机制:

所以再返回来看AtomicInteger.incrementAndGet()方法,它的时间也比较简单

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

可以看到,compareAndSet()调用的就是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。

使用示例如下图,用于标识程序执行过程中是否发生了异常,使用quartz实现高级定制化定时任务(包含管理界面)实现中:

2020-02-12补充如下内容:

  • 原子类相比于普通的锁,粒度更细、效率更高(除了高度竞争的情况下)
  • 如果对于上面的示例代码中使用了thread.yield()之类的方法不清晰的,可以直接看下面的代码压测:
public class AtomicIntegerTest implements Runnable {
 
    static AtomicInteger atomicInteger = new AtomicInteger(0);
 
    static int commonInteger = 0;
 
    public void addAtomicInteger() {
        atomicInteger.getAndIncrement();
    }
 
    public void addCommonInteger() {
        commonInteger++;
    }
 
    @Override
    public void run() {
        //可以调大10000看效果更明显
        for (int i = 0; i < 10000; i++) {
            addAtomicInteger();
            addCommonInteger();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
        Thread thread1 = new Thread(atomicIntegerTest);
        Thread thread2 = new Thread(atomicIntegerTest);
        thread1.start();
        thread2.start();
        //join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕
        thread1.join();
        thread2.join();
        System.out.println("AtomicInteger add result = " + atomicInteger.get());
        System.out.println("CommonInteger add result = " + commonInteger);
    }
}
  • 原子类一览图参考如下:

  • 如何把普通变量升级为原子变量?主要是AtomicIntegerFieldUpdater<T>类,参考如下代码:
/**
 * @description 将普通变量升级为原子变量
 **/
public class AtomicIntegerFieldUpdaterTest implements Runnable {
 
    static Goods phone;
    static Goods computer;
 
    AtomicIntegerFieldUpdater<Goods> atomicIntegerFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Goods.class, "price");
 
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            phone.price++;
            atomicIntegerFieldUpdater.getAndIncrement(computer);
        }
    }
 
    static class Goods {
        //商品定价
        volatile int price;
    }
 
    public static void main(String[] args) throws InterruptedException {
        phone = new Goods();
        computer = new Goods();
        AtomicIntegerFieldUpdaterTest atomicIntegerFieldUpdaterTest = new AtomicIntegerFieldUpdaterTest();
        Thread thread1 = new Thread(atomicIntegerFieldUpdaterTest);
        Thread thread2 = new Thread(atomicIntegerFieldUpdaterTest);
        thread1.start();
        thread2.start();
        //join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕
        thread1.join();
        thread2.join();
        System.out.println("CommonInteger price = " + phone.price);
        System.out.println("AtomicInteger price = " + computer.price);
    }
}
  • 在高并发情况下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的,参考以下压测代码:
/**
 * @description 压测AtomicLong的原子操作性能
 **/
public class AtomicLongTest implements Runnable {
 
    private static AtomicLong atomicLong = new AtomicLong(0);
 
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            atomicLong.incrementAndGet();
        }
    }
 
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(30);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            es.submit(new AtomicLongTest());
        }
        es.shutdown();
        //保证任务全部执行完
        while (!es.isTerminated()) { }
        long end = System.currentTimeMillis();
        System.out.println("AtomicLong add 耗时=" + (end - start));
        System.out.println("AtomicLong add result=" + atomicLong.get());
    }
}
/**
 * @description 压测LongAdder的原子操作性能
 **/
public class LongAdderTest implements Runnable {
 
    private static LongAdder longAdder = new LongAdder();
 
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            longAdder.increment();
        }
    }
 
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(30);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            es.submit(new LongAdderTest());
        }
        es.shutdown();
        //保证任务全部执行完
        while (!es.isTerminated()) {
        }
        long end = System.currentTimeMillis();
        System.out.println("LongAdder add 耗时=" + (end - start));
        System.out.println("LongAdder add result=" + longAdder.sum());
    }
}

在高度并发竞争情形下,AtomicLong每次进行add都需要flush和refresh(这一块涉及到java内存模型中的工作内存和主内存的,所有变量操作只能在工作内存中进行,然后写回主内存,其它线程再次读取新值),每次add()都需要同步,在高并发时会有比较多冲突,比较耗时导致效率低;而LongAdder中每个线程会维护自己的一个计数器,在最后执行LongAdder.sum()方法时候才需要同步,把所有计数器全部加起来,不需要flush和refresh操作。

rk3568_atomic 本文记录的是正点原子rk3568开发板的atomic实验提示:以下是本篇文章正文内容,下面案例可供参考例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。原子操作避免了 全局变量被其他线程修改的情况。 阅读详情

相关推荐

Linux内核4.14版本——drm框架分析(15)——drm_atomic_commit之drm_atomic_check_only

drm

yangguoyu8023的博客 1824

并发编程之AtomicReference

简介 今天我们来学习另外几个开发过程中可能会用到的原子类,或者在面试的时候会被问到的类。 分别是: 1、AtomicReference 2、AtomicStampedReference 3、AtomicArray 4、AtomicFieldUpdater 我们在前两节学习的类,都是针对基础类型地原子性读写而设计的,这以上几个都是为引用类型地原子性操作而设计的,那如何使用他们呢?使用场景又是如何呢? AtomicReference 该类提供了对象引用的非阻塞原子性读写操作 那老规矩,我们还是来看看

song1995924的博客 736

C++11多线程(三):atomic与atomic_flag的使用与原理分析

C++11提供了线程的原子操作,这些在boost库中也有,在多个线程时原子操作保证了数据的安全性。C++11的原子操作主要是atomic和atomic_flag,使用时需要包含如下头文件: #include<atomic> 一.atmoic类的使用 template<>struct atomic<_ITYPE> ,...

琅嬛福地 4378

源码看JAVA【三十一】AtomicReference

说明:AtomicReferenc&lt;V&gt;:泛型原子操作,提供了原子操作的设置值操作,并没有自增自减等具有数值特性的方法,相对来说较为简单。仅提供了原子操作的通用方法。针对AtomicInteger为何不继承AtomicReference,因为底层针对特定的类型具有更高的性能。 1、声明与定义 private static final Unsafe unsafe = Uns...

只因有你,在路上。。。 329

高效网络流量回放:使用tcpreplay实现多目标IP的pcap重放

本文详细介绍了如何使用tcpreplay工具高效回放网络流量pcap文件,并实现多目标IP发送。通过tcprewrite编辑报文和并行回放策略,可满足安全测试、负载测试等场景需求。文章涵盖安装、基础回放、核心技巧及性能优化,帮助读者构建真实流量压力测试环境。

weixin_29297127的博客 479

【文献阅读笔记】之基于注意力机制的深度学习路面裂缝检测

中文,计算机辅助设计与图形学学报,第 32 卷 第 8 期,2020 年 8 月。 DOI: 10.3724/SP.J.1089.2020.18059 摘要: 为实现自动准确地检测路面裂缝, 提升路面裂缝检测效果, 提出了一种基于注意力机制的裂缝检测网络 (attention-based crack networks, ACNet). 该网络采用编码器-解码器网络构架, 编码器采用 ResNet34 为骨干网, 提取路面裂缝特征; 在编码器和解码器间加入基于注意力机制的特征模块(attention-b

qq_44785998的博客 7592

Java Atomic总结

所谓 Atomic,翻译过来就是原子。原子被认为是操作中最小的单位,一段代码如果是原子的,则表示这段代码在执行过程中,要么执行成功,要么执行失败。原子操作一般都是底层通过 CPU 的指令来实现。而 atomic 包下的这些类,则可以让我们在多线程环境下,通过一种无锁的原子操作来实现线程安全。 atomic 包下的类基本上都是借助 Unsafe 类,通过 CAS 操作来封装实现的。Unsafe 这...

芒果不是程序猿v: 的博客 567

PX4从放弃到精通(二十八):垂起过渡控制

固件版本:1.14.0可加名片交流学习。

超维空间科技的博客 2873

高并发下的 AtomicReference 性能陷阱

我本以为 AtomicReference 也有着类似的性能,结果发现在 16C32G 机器上 16 个线程并发执行 AtomicReference 的 compareAndSet() 操作只有 200多万的ops. 调用 CAS 时一般会搭配自旋,此时 16 个核全部打满也才 200 多万ops。各大网站搜了一下没有讲 AtomicReference 性能的。一般情况下 AtomicInteger、AtomicLong 的性能随着线程的增多而急速下降,高并发下甚至不如加锁的版本。大概是类似下面的场景。

792

Qt C++ 的虚拟演播室系统

**数据流转**:OpenCV 处理的图像格式通常是 `cv::Mat`,而 Qt 3D 渲染需要 `QImage` 或 `QTexture2D`。// 临时用颜色代替。// 前景在背景前面。#### 3.3 主窗口与 Qt 3D 集成 (`mainwindow.h` / `.cpp` / `.ui`)* **更复杂的 3D 场景**:加载 3D 模型(.obj, .gltf 等)作为背景和道具。1. **构建项目**:确保 `.pro` 文件中的 OpenCV 路径配置正确,然后构建项目。

寒寒豆豆软件公司的博客 111

贝叶斯(Bayes)决策理论

在博文【模式识别PR和模式的概念】介绍了相关的模式识别与模式的概念。在本节描述所要讨论的问题之前,再提一下对于待识别的物理对象的描述问题。假设一个待识别的物理对象用其d个属性观察值描述,称之为d个特征,这组成一个d维的特征向量,而这d维待征所有可能的取值范围则组成了一个d维的特征空间。一般地,机器实现自动分类有两大类方法:一种是模板匹配方法,另一种是对特征空间划分为子空间(每类的势力范围)的方法。

松子茶的专栏 3万+

【C++】atomic原子操作

C++中原子变量(atomic)是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰,从而避免竞态条件(race condition)和死锁(deadlock)等问题。C++11起提供了atomic,可以使用它定义一个原子类型。

qq_60755751的博客 2440

RK3588平台开发系列讲解(SARADC篇)SARADC的工作流程

📢本篇将介绍SARADC的源码分析和工作流程。

内核笔记 3231

muduo之Atomic

Atomic是muduo原子操作的实现类。 Atomic.h // Use of this source code is governed by a BSD-style license // that can be found in the License file. // // Author: Shuo Chen (chenshuo at chenshuo dot com...

1万+

YOLOv8工业缺陷检测实战:攻克光照不均难题,精度从70%到92%的全流程优化(附缺陷数据集)

YOLOv8在工业缺陷检测中突破光照不均难题,核心是“硬件控光+数据造景+模型聚焦+训练攻坚硬件控光:从源头减少光照变异,降低算法难度;数据造景:用CLAHE、合成样本让模型“见过足够多的光照情况”;模型聚焦:用ECA注意力和Focal-EIoU损失,让模型盯住缺陷而非光照;训练攻坚:分阶段+难例挖掘,强迫模型学习薄弱点。这套方案已在汽车轴承、手机外壳、PCB板3类产线落地,平均精度提升20%+,漏检率控制在0.08%以内,完全满足工业级要求。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1195

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

【C++11并发】Atomic 笔记

atomic相关操作

A_cainiao_A 1562

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Linux原子操作 atomic_cmpxchg()/Atomic_read()/Atomic_set()/Atomic_add()/Atomic_sub()

所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。因此这里的原子实际是使用了物理学里的物质微粒的概念。 原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。 原子操作主要用于实

兰宝的专栏 4965

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

原子操作__atomic_store and __atomic_load

链接:https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html https://blog.youkuaiyun.com/weixin_36145588/article/details/73847365 http://www.verydoc.net/c/00000145.html 原子操作在多线程开发中经常用到,比如在计数器,序列产生器等地方,这类情况下数据有并发的危险,但是用锁去保护又显得有些浪费,所以原子类型操作十分的方便。 原子操作虽

phone1126的专栏 1万+

C++11 atomic

C++11 atomic库介绍

羊羊羊 1006

linux 原子性操作atomic_t

在任何处理器平台下,都会有一些原子性操作,供操作系统使用,我们这里只讲x86下面的。在单处理器情况下,每条指令的执行都是原子性的,但在多处理器情况下,只有那些单独的读操作或写操作才是原子性的。为了弥补这一缺点,x86提供了附加的lock前缀,使带lock前缀的读修改写指令也能原子性执行。带lock前缀的指令在操作时会锁住总线,使自身的执行即使在多处理器间也是原子性执行的。xchg指令不带lock前

晴天的专栏 5225

atomic头文件编译_c++11 多线程(3)atomic 总结

一、头文件结构头文件:原子类型是封装了一个值的类型,它的访问保证不会导致数据的竞争,并且可以用于在不同的线程之间同步内存访问。这个头声明了两个c++类,原子和atomic_flag,它实现了自包含类中的原子类型的所有特性。header还声明了整个C样式类型和函数与C中的原子支持兼容。头文件中结构定义查看。头文件的结构:class用于bool、整数和指针类型的原子类模板和特殊化 (类模板)无锁布尔原...

weixin_35578211的博客 2212

atomic 原子操作

修改一个变量会经过读、修改、写的操作序列。但有时该操作序列在执行完毕前会被其他任务或事件打断。比如在多CPU体系结构中,运行在两个CPU上的两个内核控制路径同时并行执行上面操作序列,有可能发生下面的场景:linux专门定义了一种只进行原子操作的类型atomic_t。

lzhf1122的博客 2422

C++11多线程:atomic头文件

原子类型是一种对数据的封装,防止

呆瓜的博客 9020

STL::atomic原子操作

原文地址:https://www.cnblogs.com/haippy/p/3252056.html C++11 并发指南已经写了 5 章,前五章重点介绍了多线程编程方面的内容,但大部分内容只涉及多线程、互斥量、条件变量和异步编程相关的 API,C++11 程序员完全可以不必知道这些 API 在底层是如何实现的,只需要清楚 C++11 多线程和异步编程相关 API 的语义,然后熟加练习即可应付大...

小米的修行之路 3325

Java原子类Atomic详解

解决并发的线程安全问题有两种方式: 1、加锁 2、原子类

qq_40276626的博客 1万+
上一篇: CyclicBarrier原理
下一篇: Elasticsearch倒排索引
wrr-cat
博客等级 码龄9年 128粉丝 302原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值