Atomic实现多线程下,无锁的进行原子操作

简介: Atomic实现多线程下,无锁的进行原子操作

Java1.5的Atomic包名为java.util.concurrent.atomic。这个包提供了一系列原子类。这些类可以保证多线程环境下,当某个线程在执行atomic的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个线程执行。Atomic类在软件层面上是非阻塞的,它的原子性其实是在硬件层面上借助相关的指令来保证的。


Atomic包核心

  • Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作;

图片.png

关于CAS

  • compare and swap,比较和替换技术,将预期值与当前变量的值比较(compare),如果相等则使用新值替换(swap)当前变量,否则不作操作;
  • 现代CPU已广泛支持CAS指令,如果不支持,那么JVM将使用自旋锁,与互斥锁一样,两者都需先获取锁才能访问共享资源,但互斥锁会导致线程进入睡眠,而自旋锁会一直循环等待直到获取锁
  • 另外,有一点需要注意的是CAS操作中的ABA问题,即将预期值与当前变量的值比较的时候,即使相等也不能保证变量没有被修改过,因为变量可能由A变成B再变回A,解决该问题,可以给变量增加一个版本号,每次修改变量时版本号自增,比较的时候,同时比较变量的值和版本号即可;


CAS线程安全

  • 可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!
  • 虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小,型如计数器这样的需求用起来才有效,否则也不会有锁的存在了。

Atomic包介绍

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


原子更新基本类型类

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

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


AtomicInteger的常用方法如下:

图片.png

  • get()             直接返回值
  • getAndAdd(int)    增加指定的数据,返回变化前的数据
  • getAndDecrement() 减少1,返回减少前的数据
  • getAndIncrement() 增加1,返回增加前的数据
  • getAndSet(int)    设置指定的数据,返回设置前的数据
  • addAndGet(int)    增加指定的数据后返回增加后的数据
  • decrementAndGet() 减少1,返回减少后的值
  • incrementAndGet() 增加1,返回增加后的值
  • lazySet(int)      仅仅当get时才会set
  • compareAndSet(int, int) 尝试新增后对比,若增加成功则返回true否则返回false

AtomicInteger例子代码如下:

import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
  static AtomicInteger ai = new AtomicInteger(1);
  public static void main(String[] args) {
    System.out.println(ai.getAndIncrement());
    System.out.println(ai.get());
  }
}

输出

1
2


餐后甜点

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

原子更新数组类

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

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

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

图片.png

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

实例代码如下:

public class AtomicIntegerArrayTest {
  static int[] value = new int[] { 1, 2 };
  static AtomicIntegerArray ai = new AtomicIntegerArray(value);
  public static void main(String[] args) {
    ai.getAndSet(0, 3);
    System.out.println(ai.get(0));
                System.out.println(value[0]);
  }
}


输出

3
1


       AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。

原子更新引用类型

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

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicReference的使用例子代码如下:

图片.png

import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
    public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
    public static void main(String[] args) {
        User user = new User("FLY", 25);
        atomicUserRef.set(user);
        System.out.println("【set后】:"+user);
        User updateUser = new User("琦彦", 23);
        atomicUserRef.compareAndSet(user, updateUser);
        System.out.println("【compareAndSet后】:"+updateUser);
        System.out.println(atomicUserRef.get().getName());
        System.out.println(atomicUserRef.get().getOld());
    }
    static class User {
        private String name;
        private int old;
        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", old=" + old +
                    '}';
        }
    }
}



输出

【set后】:User{name='FLY', old=25}
【compareAndSet后】:User{name='琦彦', old=23}
琦彦
23



原子更新字段类

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

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。

原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。AtomicIntegerFieldUpdater的例子代码如下:

图片.png

package service;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest {
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
            .newUpdater(User.class, "old");
    public static void main(String[] args) {
        User fly = new User("FLY", 10);
        System.out.println(a.getAndIncrement(fly));
        System.out.println(a.get(fly));
    }
    public static class User {
        private String name;
        public volatile int old;
        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", old=" + old +
                    '}';
        }
    }
}

输出

10
11



参考来源:https://www.cnblogs.com/chenpi/p/5375805.html

参考来源:http://ifeve.com/java-atomic/



目录
相关文章
|
6月前
|
安全 Java API
Java多线程编程:使用Atomic类实现原子操作
【4月更文挑战第6天】Java的`java.util.concurrent.atomic`包提供了一系列原子类,如`AtomicInteger`和`AtomicLong`,利用CPU原子指令保证无锁情况下变量更新的原子性,从而实现线程安全。这些类在高并发场景下能避免线程阻塞,提高性能。`AtomicInteger`和`AtomicLong`支持原子地增加、减少和设置值,而`AtomicReference`则适用于原子更新引用对象。尽管原子类具有非阻塞、线程安全和易用等优点,但它们仅保证单个变量的原子性,复杂操作可能仍需传统同步机制。了解其工作原理和局限性,有助于提升并发应用性能。
98 0
|
6月前
多线程并发锁的方案—原子操作
多线程并发锁的方案—原子操作
|
3月前
|
安全 Java API
Java多线程编程:使用Atomic类实现原子操作
在Java多线程环境中,共享资源的并发访问可能导致数据不一致。传统的同步机制如`synchronized`关键字或显式锁虽能保障数据一致性,但在高并发场景下可能导致线程阻塞和性能下降。为此,Java提供了`java.util.concurrent.atomic`包下的原子类,利用底层硬件的原子操作确保变量更新的原子性,实现无锁线程安全。
32 0
|
4月前
|
算法 编译器 C++
开发与运维线程问题之在C++的原子操作中memory_order如何解决
开发与运维线程问题之在C++的原子操作中memory_order如何解决
43 2
|
6月前
|
存储 安全 Java
java多线程之原子操作类
java多线程之原子操作类
|
6月前
|
设计模式 并行计算 安全
【C/C++ 多线程编程】深入探讨双检锁与原子操作
【C/C++ 多线程编程】深入探讨双检锁与原子操作
208 0
多线程的原子操作
多线程的原子操作
65 0
|
6月前
|
Linux C语言
linux c 多线程 互斥锁、自旋锁、原子操作的分析与使用
生活中,我们常常会在12306或者其他购票软件上买票,特别是春节期间或者国庆长假的时候,总会出现抢票的现象,最后总会有人买不到票而埋怨这埋怨那,其实这还好,至少不会跑去现场或者网上去找客服理论,如果出现了付款,但是却没买到票的现象,那才是真的会出现很多问题,将这里的票引入到多线程中,票就被称为临界资源。
80 0
|
6月前
|
安全 Linux 调度
Linux C/C++ 开发(学习笔记四):多线程并发锁:互斥锁、自旋锁、原子操作、CAS
Linux C/C++ 开发(学习笔记四):多线程并发锁:互斥锁、自旋锁、原子操作、CAS
83 0
|
存储 API C#
C#多线程系列(3):原子操作
C#多线程系列(3):原子操作
349 0
C#多线程系列(3):原子操作