精华内容
下载资源
问答
  • 原子操作根据百度百科的定义:"原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会...

    921bf5c65445

    题图:by pixel2013 From pixabay

    上期我们介绍了Java8中新的时间日期API,本期我们介绍Java8中原子性操作LongAdder。

    原子操作

    根据百度百科的定义:

    "原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

    AtomicLong

    在单线程的环境中,使用Long,如果对于多线程的环境,如果使用Long的话,需要加上synchronized关键字,从Java5开始,JDK提供了AtomicLong类,AtomicLong是一个提供原子操作的Long类,通过线程安全的方式操作加减,AtomicLong提供原子操作来进行Long的使用,因此十分适合高并发情况下的使用。

    public class AtomicLongFeature {

    private static final int NUM_INC = 1_000_000;

    private static AtomicLong atomicLong = new AtomicLong(0);

    private static void update() {

    atomicLong.set(0);

    ExecutorService executorService = Executors.newFixedThreadPool(5);

    IntStream.range(0, NUM_INC).forEach(i -> {

    Runnable task = () -> atomicLong.updateAndGet(n -> n + 2);

    executorService.submit(task);

    });

    stop(executorService);

    System.out.println(atomicLong.get());

    }

    private static void stop(ExecutorService executorService) {

    try {

    executorService.shutdown();

    executorService.awaitTermination(60, TimeUnit.SECONDS);

    } catch (InterruptedException e) {

    e.printStackTrace();

    } finally {

    if (!executorService.isTerminated()) {

    System.out.println("kill tasks");

    }

    executorService.shutdownNow();

    }

    }

    public static void main(String[] args) {

    update();

    }

    }

    输出:

    2000000

    为什么AtomicInteger能支持高并发呢?看下AtomicLong的updateAndGet方法:

    public final int updateAndGet(IntUnaryOperator updateFunction) {

    int prev, next;

    do {

    prev = get();

    next = updateFunction.applyAsInt(prev);

    } while (!compareAndSet(prev, next));

    return next;

    }

    public final boolean compareAndSet(int expect, int update) {

    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

    }

    原因是每次updateAndGet时都会调用compareAndSet方法。

    AtomicLong是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合,但并不能每一种场景都适合,不同场景要使用使用不同的数值类。

    LongAdder

    AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。那么LongAdder又是使用到了什么原理?难道有比cas更加快速的方式?

    public class LongAdderFeature {

    private static final int NUM_INC = 1_000_000;

    private static LongAdder longAdder = new LongAdder();

    private static void update() {

    ExecutorService executorService = Executors.newFixedThreadPool(5);

    IntStream.range(0, NUM_INC).forEach(i -> {

    Runnable task = () -> longAdder.add(2);

    executorService.submit(task);

    });

    stop(executorService);

    System.out.println(longAdder.sum());

    }

    private static void stop(ExecutorService executorService) {

    try {

    executorService.shutdown();

    executorService.awaitTermination(60, TimeUnit.SECONDS);

    } catch (InterruptedException e) {

    e.printStackTrace();

    } finally {

    if (!executorService.isTerminated()) {

    System.out.println("kill tasks");

    }

    executorService.shutdownNow();

    }

    }

    public static void main(String[] args) {

    update();

    }

    }

    输出:

    2000000

    我们来看下LongAdder的add方法:

    public void add(long x) {

    Cell[] as; long b, v; int m; Cell a;

    if ((as = cells) != null || !casBase(b = base, b + x)) {

    boolean uncontended = true;

    if (as == null || (m = as.length - 1) < 0 ||

    (a = as[getProbe() & m]) == null ||

    !(uncontended = a.cas(v = a.value, v + x)))

    longAccumulate(x, null, uncontended);

    }

    }

    我们可以看到一个Cell的类,那这个类是用来干什么的呢?

    @sun.misc.Contended static final class Cell {

    volatile long value;

    Cell(long x) { value = x; }

    final boolean cas(long cmp, long val) {

    return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);

    }

    // Unsafe mechanics

    private static final sun.misc.Unsafe UNSAFE;

    private static final long valueOffset;

    static {

    try {

    UNSAFE = sun.misc.Unsafe.getUnsafe();

    Class> ak = Cell.class;

    valueOffset = UNSAFE.objectFieldOffset

    (ak.getDeclaredField("value"));

    } catch (Exception e) {

    throw new Error(e);

    }

    }

    }

    我们可以看到Cell类的内部是一个volatile的变量,然后更改这个变量唯一的方式通过cas。我们可以猜测到LongAdder的高明之处可能在于将之前单个节点的并发分散到各个节点的,这样从而提高在高并发时候的效率。

    LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。

    public long sum() {

    Cell[] as = cells; Cell a;

    long sum = base;

    if (as != null) {

    for (int i = 0; i < as.length; ++i) {

    if ((a = as[i]) != null)

    sum += a.value;

    }

    }

    return sum;

    }

    当计数的时候,将base和各个cell元素里面的值进行叠加,从而得到计算总数的目的。这里的问题是在计数的同时如果修改cell元素,有可能导致计数的结果不准确,所以缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

    展开全文
  • golang中的原子操作

    2021-01-20 21:48:47
    简介 加锁代价比较耗时,需要上下文切换 针对基本数据类型,可以使用原子操作保证线程安全 ...只要原子操作函数拿到了被操作的指针,就可以定位到存储该的内存地址。只有这样,它们才能够通过底层的指令,准确地操

    简介

    • 加锁代价比较耗时,需要上下文切换
    • 针对基本数据类型,可以使用原子操作保证线程安全
    • 原子操作在用户态就可以完成,因此性能比互斥锁要高

    sync/atomic包中的函数可以做的原子操作有:

    • 加法(add)
    • 比较并交换(compare and swap,简称 CAS)
    • 加载(load)
    • 存储(store)
    • 交换(swap)

    原子操作函数需要的是被操作值的指针,而不是这个值本身

    只要原子操作函数拿到了被操作值的指针,就可以定位到存储该值的内存地址。只有这样,它们才能够通过底层的指令,准确地操作这个内存地址上的数据。

    支持的类型

    这些函数针对的数据类型并不多。但是,对这些类型中的每一个,

    sync/atomic包都会有一套函数给予支持。这些数据类型有:int32、int64、uint32、uint64、uintptr,以及unsafe包中的Pointer。

    不过,针对unsafe.Pointer类型,该包并未提供进行原子加法操作的函数。

    减法

    atomic.AddInt32函数的第二个参数代表差量,它的类型是int32,是有符号的。如果我们想做原子减法,那么把这个差量设置为负整数就可以了。

    对于atomic.AddInt64函数来说也是类似的。不过,要想用atomic.AddUint32和atomic.AddUint64函数做原子减法,就不能这么直接了,因为它们的第二个参数的类型分别是uint32和uint64,都是无符号的,不过,这也是可以做到的,就是稍微麻烦一些。

    例如,如果想对uint32类型的被操作值18做原子减法,比如说差量是-3,那么我们可以先把这个差量转换为有符号的int32类型的值,然后再把该值的类型转换为uint32,用表达式来描述就是uint32(int32(-3))。

    不过要注意,直接这样写会使 Go 语言的编译器报错,它会告诉你:“常量-3不在uint32类型可表示的范围内”,换句话说,这样做会让表达式的结果值溢出。

    不过,如果我们先把int32(-3)的结果值赋给变量delta,再把delta的值转换为uint32类型的值,就可以绕过编译器的检查并得到正确的结果了。

    最后,我们把这个结果作为atomic.AddUint32函数的第二个参数值,就可以达到对uint32类型的值做原子减法的目的了。

    还有一种更加直接的方式。我们可以依据下面这个表达式来给定atomic.AddUint32函数的第二个参数值:

    ^uint32(-N-1))
    

    其中的N代表由负整数表示的差量。也就是说,我们先要把差量的绝对值减去1,然后再把得到的这个无类型的整数常量,转换为uint32类型的值,最后,在这个值之上做按位异或操作,就可以获得最终的参数值了。

    这么做的原理也并不复杂。简单来说,此表达式的结果值的补码,与使用前一种方法得到的值的补码相同,所以这两种方式是等价的。我们都知道,整数在计算机中是以补码的形式存在的,所以在这里,结果值的补码相同就意味着表达式的等价

    比较并交换和交换操作相比有什么不同?

    比较并交换操作即 CAS 操作,是有条件的交换操作,只有在条件满足的情况下才会进行值的交换。

    所谓的交换指的是,把新值赋给变量,并返回变量的旧值。

    在进行 CAS 操作的时候,函数会先判断被操作变量的当前值,是否与我们预期的旧值相等。如果相等,它就把新值赋给该变量,并返回true以表明交换操作已进行;否则就忽略交换操作,并返回false。

    可以看到,CAS 操作并不是单一的操作,而是一种操作组合。这与其他的原子操作都不同。正因为如此,它的用途要更广泛一些。例如,我们将它与for语句联用就可以实现一种简易的自旋锁(spinlock)。

    for {
       if atomic.CompareAndSwapInt32(&num2, 10, 0) {
       fmt.Println("The second number has gone to zero.")
       break
    }
       time.Sleep(time.Millisecond * 500)
    }
    

    for语句加 CAS 操作的假设往往是:共享资源状态的改变并不频繁,或者,它的状态总会变成期望的那样。这是一种更加乐观,或者说更加宽松的做法。

    方法

    目前只支持int类型

    import (
       "fmt"
       "sync"
       "sync/atomic"
    )
    
    var x int32
    var wg sync.WaitGroup
    
    func add() {
       for i := 0; i<5000; i++ {
          //x = x+1
          atomic.AddInt32(&x, 1)
       }
       defer wg.Done()
    }
    
    func main() {
       wg.Add(2)
       //各加5000
       go add()
       go add()
       wg.Wait()
       //结果一定是10000
       fmt.Println(x)
    }
    

    自旋锁

    // forAndCAS1 用于展示简易的自旋锁。
    func forAndCAS1() {
       sign := make(chan struct{}, 2)
       num := int32(0)
       fmt.Printf("The number: %d\n", num)
       go func() { // 定时增加num的值。
          defer func() {
             sign <- struct{}{}
          }()
          for {
             time.Sleep(time.Millisecond * 500)
             newNum := atomic.AddInt32(&num, 2)
             fmt.Printf("The number: %d\n", newNum)
             if newNum == 10 {
                break
             }
          }
       }()
       go func() { // 定时检查num的值,如果等于10就将其归零。
          defer func() {
             sign <- struct{}{}
          }()
          for {
             if atomic.CompareAndSwapInt32(&num, 10, 0) {
                fmt.Println("The number has gone to zero.")
                break
             }
             time.Sleep(time.Millisecond * 500)
          }
       }()
       <-sign
       <-sign
    }
    

    atomic.value

    atomic.Value分为两个操作,通过Store()存储Value,通过Load()来读取Value的值.

    package main
    
    import (
       "fmt"
       "sync/atomic"
    )
    
    type Value struct {
       key string
       Val interface{}
    }
    
    type Noaway struct {
       Movies atomic.Value
       Total atomic.Value
    }
    
    func NewNoaway() *Noaway {
       n := new(Noaway)
       n.Movies.Store(&Value{key: "movie", Val: "Wolf Warrior 2"})
       n.Total.Store("123")
       return n
    }
    
    func main() {
       n := NewNoaway()
       val := n.Movies.Load().(*Value)
       total := n.Total.Load().(string)
       fmt.Printf("%#v --- %#v\n", val, total)
    }
    

    如何用好atomic.value

    第一条规则,不能用原子值存储nil。

    也就是说,我们不能把nil作为参数值传入原子值的Store方法,否则就会引发一个 panic。

    这里要注意,如果有一个接口类型的变量,它的动态值是nil,但动态类型却不是nil,那么它的值就不等于nil。我在前面讲接口的时候和你说明过这个问题。正因为如此,这样一个变量的值是可以被存入原子值的。

    第二条规则,我们向原子值存储的第一个值,决定了它今后能且只能存储哪一个类型的值。

    例如,我第一次向一个原子值存储了一个string类型的值,那我在后面就只能用该原子值来存储字符串了。如果我又想用它存储结构体,那么在调用它的Store方法的时候就会引发一个 panic。这个 panic 会告诉我,这次存储的值的类型与之前的不一致。

    你可能会想:我先存储一个接口类型的值,然后再存储这个接口的某个实现类型的值,这样是不是可以呢?

    很可惜,这样是不可以的,同样会引发一个 panic。因为原子值内部是依据被存储值的实际类型来做判断的。所以,即使是实现了同一个接口的不同类型,它们的值也不能被先后存储到同一个原子值中。

    遗憾的是,我们无法通过某个方法获知一个原子值是否已经被真正使用,并且,也没有办法通过常规的途径得到一个原子值可以存储值的实际类型。这使得我们误用原子值的可能性大大增加,尤其是在多个地方使用同一个原子值的时候。

    下面,我给你几条具体的使用建议。

    1. 不要把内部使用的原子值暴露给外界。比如,声明一个全局的原子变量并不是一个正确的做法。这个变量的访问权限最起码也应该是包级私有的。

    2. 如果不得不让包外,或模块外的代码使用你的原子值,那么可以声明一个包级私有的原子变量,然后再通过一个或多个公开的函数,让外界间接地使用到它。注意,这种情况下不要把原子值传递到外界,不论是传递原子值本身还是它的指针值。

    3. 如果通过某个函数可以向内部的原子值存储值的话,那么就应该在这个函数中先判断被存储值类型的合法性。若不合法,则应该直接返回对应的错误值,从而避免 panic 的发生。

    4. 如果可能的话,我们可以把原子值封装到一个数据类型中,比如一个结构体类型。这样,我们既可以通过该类型的方法更加安全地存储值,又可以在该类型中包含可存储值的合法类型信息。

    除了上述使用建议之外,我还要再特别强调一点:尽量不要向原子值中存储引用类型的值。因为这很容易造成安全漏洞。请看下面的代码:

    var box6 atomic.Value
    v6 := []int{1, 2, 3}
    box6.Store(v6)
    v6[1] = 4 // 注意,此处的操作不是并发安全的!
    

    我把一个[]int类型的切片值v6, 存入了原子值box6。注意,切片类型属于引用类型。所以,我在外面改动这个切片值,就等于修改了box6中存储的那个值。这相当于绕过了原子值而进行了非并发安全的操作。那么,应该怎样修补这个漏洞呢?可以这样做:

    store := func(v []int) {
       replica := make([]int, len(v))
       copy(replica, v)
       box6.Store(replica)
    }
    store(v6)
    v6[2] = 5 // 此处的操作是安全的
    展开全文
  • 现在,我需要一种在一次原子操作中替换该映射的所有的方法。public final class StatefulBean {private final Map state = new ConcurrentSkipListMap<>();public StatefulBean() {//Initial statethis.s...

    我在多线程环境中有一个有状态的Bean,它将其状态保存在映射中。现在,我需要一种在一次原子操作中替换该映射的所有值的方法。

    public final class StatefulBean {

    private final Map state = new ConcurrentSkipListMap<>();

    public StatefulBean() {

    //Initial state

    this.state.put("a", "a1");

    this.state.put("b", "b1");

    this.state.put("c", "c1");

    }

    public void updateState() {

    //Fake computation of new state

    final Map newState = new HashMap<>();

    newState.put("b", "b1");

    newState.put("c", "c2");

    newState.put("d", "d1");

    atomicallyUpdateState(newState);

    /*Expected result

    * a: removed

    * b: unchanged

    * C: replaced

    * d: added*/

    }

    private void atomicallyUpdateState(final Map newState) {

    //???

    }

    }

    目前,我ConcurrentSkipListMap用作的实现ConcurrentMap,但这不是必需的。

    我认为解决此问题的唯一方法是制作全局state

    volatile地图并完全替换地图或使用AtomicReferenceFieldUpdater。有没有更好的办法?

    我的更新非常频繁,每秒一次或两次,但是机会很少。同样,整个地图只会包含少于20个值。

    展开全文
  • Atomic 原子

    2021-11-30 16:25:12
    本文目录1.基本类型1.AtomicInteger2.AtomicLong3.AtomicBoolean2.引用类型1.AtomicReference2.AtomicMarkableReference3....原子更新字段类1.AtomicIntegerFieldUpdater2.AtomicLongFieldUpdater3.Atomi


      在 Atomic 包里一共有12个类,四种原子更新方式,分别是: 原子更新基本类型原子更新数组原子更新引用原子更新字段。Atomic 包里的类基本都是使用 Unsafe 实现的包装类。

    • 基本类型:AtomicIntegerAtomicLongAtomicBoolean
    • 引用类型:AtomicReferenceAtomicStampedRerenceAtomicMarkableReference
    • 数组类型:AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray
    • 原子更新字段类:AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater

    1.基本类型

    用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:

    • AtomicBoolean:原子更新布尔类型
    • AtomicInteger:原子更新整型
    • AtomicLong:原子更新长整型。

    AtomicInteger的常用方法如下:

    • int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
    • boolean compareAndSet(int expect, int update) :如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
    • int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
    • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
    • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

      Atomic 包提供了三种基本类型的原子更新,但是 Java 的基本类型里还有 char,float 和 double 等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic 包里的类基本都是使用 Unsafe 实现的Unsafe 只提供了三种 CAS 方法,compareAndSwapObject,compareAndSwapInt 和 compareAndSwapLong,再看 AtomicBoolean 源码,发现其是先把 Boolean 转换成整型,再使用compareAndSwapInt 进行 CAS,所以原子更新 double 也可以用类似的思路来实现。

    1.AtomicInteger

    public class AtomicIntegerTest {
        
        static AtomicInteger atomicInteger = new AtomicInteger(); // 不写初始值,默认从0开始
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> atomicInteger.incrementAndGet()).start();
            }
            //保证所有线程执行完成(或者休眠1s也可)
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
            System.out.println("自加10次数值:--->" + atomicInteger.get());
        }
    }
    

    2.AtomicLong

      类似 AtomicInteger

    3.AtomicBoolean

      类似 AtomicInteger

    2.引用类型

      原子更新基本类型的 AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic 包提供了以下三个类:

    • AtomicReference:原子更新引用类型
    • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
    • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。

    1.AtomicReference

      需要先构造一个引用对象然后调用AtomicReference中的相关原子方法,我们先来看一段代码示例:

    代码示例:

    package com.zwx.concurrent.atomic;
    
    import com.alibaba.fastjson.JSONObject;
    import java.util.concurrent.atomic.AtomicReference;
    
    public class TestAtomicReference {
        public static void main(String[] args) {
            User oldUser = new User(18,"张三");
            AtomicReference<User> atomicReference = new AtomicReference<>(oldUser);
            System.out.println("CAS前:" + JSONObject.toJSONString(atomicReference.get()));//{"age":18,"name":"张三"}
    
            User upateUser = new User(28,"李四");
            boolean result =atomicReference.compareAndSet(oldUser,upateUser);
            System.out.println("CAS结果为:" + result);//true
            System.out.println("CAS后:" + JSONObject.toJSONString(atomicReference.get()));//{"age":28,"name":"李四"}
        }
    }
    
    class User{
        volatile Integer age;
        private String name;
    
        public User(Integer age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    2.AtomicMarkableReference

    这个和上面 AtomicReferenc e基本一致,唯一的区别是多了一个 mark 标记,该标记为 boolean 类型。

    package com.zwx.concurrent.atomic;
    
    import com.alibaba.fastjson.JSONObject;
    import java.util.concurrent.atomic.AtomicMarkableReference;
    
    public class TestAtomicReferenceMark {
        public static void main(String[] args) {
        
            Person person = new Person(18,"张三");
            AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(person,false);
            System.out.println("是否被标记过:" + atomicMarkableReference.isMarked());
    
            System.out.println("CAS前:" + JSONObject.toJSONString(atomicMarkableReference.getReference()));//{"age":18,"name":"张三"}
    
            Person updatePerson = new Person(28,"李四");
            /**
             * arg1:表示预期的引用对象
             * arg2:表示即将更新的引用对象
             * arg3:表示预期的标记
             * arg4:表示更新的标记
             * 需要参数1和参数3都是预期值才会CAS成功
             */
            atomicMarkableReference.compareAndSet(person,updatePerson,false,true);
            System.out.println("CAS后:" + JSONObject.toJSONString(atomicMarkableReference.getReference()));//{"age":28,"name":"李四"}
        }
    }
    
    class Person{
        private Integer age;
        private String name;
    
        public Person(Integer age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    3.AtomicStampedReference

      这个和 AtomicMarkableReference 几乎一模一样,唯一的区别就是 AtomicMarkableReference 中的标记只有 true 和 false ,而AtomicStampedReference 中的标记是一个 int 类型,可以视作版本号,可以解决 CAS 的 ABA 问题

    /**
     * AtomicStampedReference 解决 ABA 问题
     */
    public class AtomicStampedReferenceTest {
    
    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);   // initialStamp 为初始版本号
    
        public static void main(String[] args) {
            Thread main = new Thread(() -> {
                int stamp = atomicStampedRef.getStamp(); //获取当前标识别
                System.out.println("操作线程" + Thread.currentThread() + "stamp=" + stamp + ",初始值 a = " + atomicStampedRef.getReference());
                try {
                    Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean isCASSuccess = atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1);  //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
                System.out.println("操作线程" + Thread.currentThread() + "stamp=" + stamp + ",CAS操作结果: " + isCASSuccess);
            }, "主操作线程");
    
            Thread other = new Thread(() -> {
                int stamp = atomicStampedRef.getStamp();
                atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1);
                System.out.println("操作线程" + Thread.currentThread() + "stamp=" + atomicStampedRef.getStamp() + ",【increment】 ,值 = " + atomicStampedRef.getReference());
                stamp = atomicStampedRef.getStamp();
                atomicStampedRef.compareAndSet(2, 1, stamp, stamp + 1);
                System.out.println("操作线程" + Thread.currentThread() + "stamp=" + atomicStampedRef.getStamp() + ",【decrement】 ,值 = " + atomicStampedRef.getReference());
            }, "干扰线程");
    
            main.start();
            other.start();
        }
    }
    

    CAS 的 ABA 问题

    /**
     * ABA 问题演示
     */ 
    public class CASABATest {
    
        static AtomicInteger atomicInteger = new AtomicInteger(1);
    
        public static void main(String[] args) {
            Thread main = new Thread(()->{
                int a = atomicInteger.get();
                System.out.println("操作线程"+Thread.currentThread().getName()+"--修改前操作数值:"+a);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean isCasSuccess = atomicInteger.compareAndSet(a,2);
                if(isCasSuccess){
                    System.out.println("操作线程"+Thread.currentThread().getName()+"--CAS修改后操作数值:"+atomicInteger.get());
                }else{
                    System.out.println("CAS修改失败");
                }
            },"主线程");
    
            Thread other = new Thread(()->{
                atomicInteger.incrementAndGet(); // 1+1 = 2;
                System.out.println("操作线程"+Thread.currentThread().getName()+"--increase后值:"+atomicInteger.get());
                atomicInteger.decrementAndGet(); // atomic-1 = 2-1;
                System.out.println("操作线程"+Thread.currentThread().getName()+"--decrease后值:"+atomicInteger.get());
            },"干扰线程");
    
            main.start();
            other.start();
        }
    }
    

    3.数组类型

    通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:

    • AtomicIntegerArray:原子更新整型数组里的元素
    • AtomicLongArray:原子更新长整型数组里的元素
    • AtomicReferenceArray:原子更新引用类型数组里的元素

    AtomicIntegerArray 类主要是提供原子的方式更新数组里的整型,其常用方法如下:

    • int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加;
    • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值

    1.AtomicIntegerArray

    public class AtomicIntegerArrayTest {
    
        public static void main(String[] args) {
            int[] arr = new int[]{1, 2, 3};
            AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
            // 原数组[1,2,3] 变更为 [1,8,3]
            atomicIntegerArray.compareAndSet(1, 2, 8);
            // 原数组的值没被改变,还是2
            System.out.println(arr[1]); 
            // atomicIntegerArray值被改变成8
            System.out.println(atomicIntegerArray.get(1));
        }
    }
    

    在这里插入图片描述
    可以看到初始化之后将数组复制了一份,所以不会如果把值改变了,不会影响原有数组的值。

    2.AtomicLongArray

      使用同 AtomicIntegerArray。

    3.AtomicReferenceArray

      这个方法也是一样,唯一的区别是可以传入一个泛型,也就是说数据中的元素时自定义的对象,而不是引用对象。

    package com.zwx.concurrent.atomic;
    
    import com.alibaba.fastjson.JSONObject;
    import java.util.concurrent.atomic.AtomicReferenceArray;
    
    public class TestAtomicReferenceArray {
        public static void main(String[] args) {
            Man man = new Man(18,"张三");
            Man[] arr = new Man[]{man};
            AtomicReferenceArray<Man> atomicReferenceArray = new AtomicReferenceArray<>(arr);
            System.out.println("CAS前:" + JSONObject.toJSONString(atomicReferenceArray.get(0)));//{"age":18,"name":"张三"}
            
            Man updateMan = new Man(28,"李四");
            atomicReferenceArray.compareAndSet(0,man,updateMan);
            System.out.println("CAS前:" + JSONObject.toJSONString(atomicReferenceArray.get(0)));//{"age":28,"name":"李四"}
        }
    }
    
    class Man{
        protected volatile Integer age;
        private String name;
    
        public Man(Integer age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    4.原子更新字段类

    如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic 包提供了以下三个类:

    • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
    • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
    • AtomicReferenceFieldUpdater:原子更新引用类型里的任意指定字段。

    1.AtomicIntegerFieldUpdater

    这个是用来更新引用对象中的int类型的属性,利用反射修改属性。有以下几点需要注意:

    • 引用类型中的属性必须是 int,不能是包装类 Integer
    • 引用类型中的属性必须被 volatile 修饰
    • 引用类型中的属性不能被 private 修饰

    示例代码:

    package com.zwx.concurrent.atomic;
    
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    
    public class TestAtomicReferenceField {
        public static void main(String[] args) {
            //AtomicIntegerFieldUpdater
            Women women = new Women(18,"张三");
            //arg1:引用的对象类型 arg2:要修改的对象中的属性名
            AtomicIntegerFieldUpdater atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Women.class,"age");
            atomicIntegerFieldUpdater.compareAndSet(women,18,28);
            System.out.println("CAS后的值:" + women.getAge());//28
        }
    }
    
    class Women{
        volatile int age;
        private String name;
    
        public Women(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    2.AtomicLongFieldUpdater

    这个和上面AtomicLongFieldUpdater基本一样,用来更新long类型的属性,同样有以下几点需要注意:

    • 引用类型中的属性必须是 long,不能是包装类 Long
    • 引用类型中的属性必须被 volatile 修饰
    • 引用类型中的属性不能被 private 修饰

    3.AtomicReferenceFieldUpdater

    上面两个都是只能更新指定数据类型,而这个可以更新任意指定类型的属性。也有以下几个注意点:

    • 引用类型中的属性不能是原始数据类型,必须用对应包装类(这点和上面的两种相反)
    • 引用类型中的属性必须被volatile修饰
    • 引用类型中的属性不能被private修饰

    代码示例:

    package com.zwx.concurrent.atomic;
    
    import com.alibaba.fastjson.JSONObject;
    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
    
    public class TestAtomicReferenceField {
        public static void main(String[] args) {
            /**
             * arg1:传入引用对象类型
             * arg2:传入引用对象的属性类型
             * arg3:传入要修改的属性名
             */
            AtomicReferenceFieldUpdater atomicReferenceFieldUpdater1 = AtomicReferenceFieldUpdater.newUpdater(Women.class,Integer.class,"age");
            AtomicReferenceFieldUpdater atomicReferenceFieldUpdater2 = AtomicReferenceFieldUpdater.newUpdater(Women.class,String.class,"name");
    
            Women women = new Women(18,"张三");
            atomicReferenceFieldUpdater1.compareAndSet(women,18,28);
            atomicReferenceFieldUpdater2.compareAndSet(women,"张三","李四");
            System.out.println(JSONObject.toJSONString(women));//{"age":28,"name":"李四"}
        }
    }
    
    class Women{
        volatile Integer age;
        volatile String name;
    
        public Women(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    本文部分内容 & 示例,摘自:

    1. Java中12个原子(Atomic)操作类实现原理分析

    End

    展开全文
  • Java 原子操作类

    2021-03-09 07:44:22
    CAS(Compare And Swap),比较并更新,指令级别保证这是一个原子操作,三个运算符: 一个内存地址V,一个期望A,一个新B;基本思路:如果地址V上的和期望A相等,就把新B更新到内存,如果不是,循环(死循环...
  • 文章目录第五章——内存模型和原子操作5.1 内存模型5.1.1 对象和内存位置5.1.2 对象,内存位置和并发5.1.3 修改顺序5.2 原子操作和原子类型5.2.1 标准原子类型5.2.2 std::atomic_flag5.2.3 std::atomic\5.2.4 std::...
  • 原子操作

    2021-05-21 17:38:51
    所谓原子操作,就是"不可中断的一个或一系列操作" 。硬件级的原子操作:1)在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统...
  • 原子引用

    2021-02-10 00:04:18
    6.4原子引用 CAS实现 public class TestAtomicReference { public static void main(String[] args) { DecimalAccountCas accountCas = new DecimalAccountCas(new BigDecimal("1000")); DecimalAccount.demo...
  • 这里详细解析什么是原子变量和原子操作,只对源码中对原子变量的运用做一些简单的分析。 这个文件的代码是安卓中log模块接口部分,即应用写log时通过套接字需要将log发送到log服务器,这里是应用client端初始化时对...
  • java原子操作

    2021-03-09 07:45:08
    Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的Atomic是指不能分割成若干部分的意思。如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的。通常来说,原子指令由硬件...
  • Redis的原子自增性

    2021-01-12 20:16:30
    INCR key将key中储存的数字增一。如果key不存在,那么key的会先被初始化为0,然后再执行INCR操作。如果包含错误的类型,或字符串类型的不能表示为数字,那么返回一个错误。本操作的限制在 64 位(bit)有...
  • Java原子

    2021-02-28 13:31:22
    一、CAS什么是CAS,CAS就是Compare and SwapCAS是一种无锁算法原理:对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存V,旧的预期A,要修改的新B。当且仅当预期A和内存V相同时,将内存V修改为B,...
  • 引言Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要...
  • 原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。在多处理器上实现原子操作就变得有点复杂。本文让我们一起来聊一聊在Inter处理器和Java里是...
  • 后面,我们一起学java.util.concurrent(简称JUC )包,java在此包中增加了在并发编程中很常用的实用工具类,包括线程池、异步IO 和轻量级任务框架,锁,原子操作类,高并发集合类等。J.U.C包基本内容这几天先学习...
  • 《Linux原子操作》由会员分享,可在线阅读,更多相关《Linux原子操作(6页珍藏版)》请在人人文库网上搜索。1、实验十 原子操作实验目的掌握字符设备驱动程序编写的框架;掌握内核中原子操作函数的使用;实验原理内核...
  • C++ 原子变量

    2021-05-15 20:01:44
    在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等,如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性...
  • 原子操作与同步机制

    2021-01-12 14:16:03
    并发问题现代操作系统支持多任务的并发,并发在提高计算资源利用率的同时也带来了资源...进程1执行完“mov eax,[count]”后,寄存器eax内保存了count的0。此时,进程2被调度执行,抢占了进程1的CPU的控制权。进...
  • 什么是原子操作原子的意思是说“不能被进一步分割的粒子”,而原子操作是说“不可被终端的一个或多个系列的操作”。假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全...
  • 原子结构与原子半径如: Be(2s2) B(2s22p1) N(2s22p3) O(2s22p4) I1(kJ·mol-1) 899.5 800.6 1402.3 1314.0 原因:Be和N的原子结构电子排布为全满、半满状态,较稳定,难失去电子,电离能较大。 * * 无机化学 第十六...
  • golang 原子操作

    2021-01-12 14:46:23
    也就是说,针对某个原子操作在被进行的过程当中,CPU绝不会再去进行其它的针对该的操作。无论这些其它的操作是否为原子操作都会是这样。为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。...
  • Java原子变量

    2021-02-26 11:29:35
    为了提升性能,Java引入了原子变量,通过无锁算法(lock-free)实现多线程安全,比如CAS。原子变量只是实现多线程安全的一个手段,在对单个共享变量进行”读取-修改-写入“操作的场景下很适合,所以,...
  • java8_原子更新

    2021-02-28 13:30:42
    标签:java原子更新回顾使用Aotimic使用(自增,自减都为原子操作)private static AtomicLong atomicLong=new AtomicLong(0);private static Random random=new Random(47);private static StampedLock stampedLock=...
  • 浅析Java中的原子操作

    2021-02-12 09:57:44
    原子方式将当前加1,注意,这里返回的是自增前的 void lazySet(int newValue):JDK6所增加,最终会设置成newValue,使用lazySet设置后,可能导致其他线程在之后的一小段时间内还是可以读到旧的 int getAnd...
  • Java线程安全-原子

    2021-02-12 22:46:52
    什么是原子性如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性问题1public class Counter {volatile int i = 0;public void add() {i++;}}public class Demo1_CounterTest {...
  • Java原子类--AtomicLong

    2021-02-26 19:26:53
    AtomicLong介绍和函数列表AtomicLong是作用是对长整形进行原子操作。在32位操作系统中,64位的long 和 double 变量由于会...AtomicLong函数列表// 构造函数AtomicLong()// 创建为initialValue的AtomicLong对象Ato...
  • VC++原子操作常用API

    2021-07-22 12:02:34
    VC++并发编程中,原子操作是很常用的技术,现将常用API做一下介绍: VC++原子操作MSDN路径: ...lpAddend为长整型变量的地址,返回值为+1后的。这个函数的主要作用是原子性自增(相当于++操作)。 (2) LONG
  • CAS机制与原子

    2021-03-13 20:11:42
    CAS机制与原子性学习...一个旧A(期望操作前的)和一个新B,在操作期间先比较下旧有没有发生变化,如果没有发生变化,才交换成新,发生了变化则不交换。JAVA中的sun.misc.Unsafe类,提供了compareAndSwapI...
  • Java 原子类笔记

    2021-03-22 13:46:59
    Java的原子类JUC并发包中提供了一系列原子性操作类,这些类都是使用非阻塞算法 CAS 实现的,比使用锁性能有提升。具体实现大致相同。原子更新基本类型AtomicBoolean、AtomicInteger、AtomicLong关键实现代码public ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 307,178
精华内容 122,871
关键字:

原子值