Java多线程 原子操作类详细

目录
  • 1、What and Why
  • 2、原子更新基本类型类
  • 3、实现原理
  • 4、原子更新数组
  • 5、原子更新引用类型
  • 6、原子更新字段类

1、What and Why

原子的本意是不能被分割的粒子,而对于一个操作来说,如果它是不可被中断的一个或者一组操作,那么他就是原子操作。显然,原子操作是安全的,因为它不会被打断。

平时我们见到的很多操作看起来是原子操作,但其实是非原子操作,例如很常见的i++操作,它背后有取值、加一、写回等操作,如果有两个线程都要对 i 进行加一操作,就有可能结果把i只变成了2,这就是线程不安全的更新操作,当然我们可以使用synchronized解决,但是JUC提供了java.util.concurrent.atomic包,这个包的原子操作类提供了一种简单高效、线程安全地更新一个变量的方式。

2、原子更新基本类型类

使用原子的方式更新基本类型,Atomic包提供了以下3个类:

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

上面三个类型的方法几乎一模一样,下面以AtomicInteger为例介绍以下他们的方法

  • int addAndGet(int data):以原子操作的方式将输入data与AtomicInteger原有的值相加,并返回结果。
  • boolean compareAndSet(int expect, int update):如果输入的数值等于预期值expect,则以原子操作的方式将update赋给AtomicInteger原有的值。
  • getAndIncrement():以原子操作的方式给AtomicInteger原有的值加一,但是注意这个方法返回的值是自增前的值。
  • int getAndSet(int newValue):以原子操作的方式给AtomicInteger原有的值设置成newValue的值
  • void lazySet(int newValue):最终会设置成newValue,但是使用lazyset设置之后,可能会导致其他线程在之后的一小段时间内还可以读到旧值。
class AtomicIntegerDemo{

    static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) {

        //新建一个线程池
        ExecutorService threadPoolExecutor = new ThreadPoolExecutor(2,
                4,
                100,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

    // 新建一个线程
    threadPoolExecutor.execute(
        () -> {
          for (int i = 0; i < 10; i++) {
              atomicInteger.incrementAndGet();
          }

        });

        //新建一个线程
        threadPoolExecutor.execute(()->{
            for (int i = 0; i < 10; i++) {
                atomicInteger.incrementAndGet();
            }
        });

        System.out.println(atomicInteger.get());
        threadPoolExecutor.shutdown();
    }
}

3、实现原理

 public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

其中,unsafe类是Java用来处理一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,它使得Java拥有了类似C语言一样操作内存空间的能力。

valueOffset是字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。

public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

打开getAndAddInt()函数,可以看到这里使用了一个CAS机制的自旋锁来对v值进行赋值,关于CAS机制可以查看文章Java多线程 乐观锁和CAS机制
getIntVolatile方法用于获取对象o指定偏移量的int值,此操作具有volatile内存语义,也就是说,即使对象o指定offset的变量不是volatile的,次操作也会使用volatile语义,会强制从主存获取值,然后通过compareAndSwapInt来替换值,直到替换成功后,退出循环。

4、原子更新数组

使用原子的方式更新数组中的某个元素,Atomic包提供了以下3个类:

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

下面以AtomicIntegerArray为例介绍以下他们的方法:

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

5、原子更新引用类型

刚刚提到的只能一次更新一个变量,如果要更新多个变量就需要使用原子更新引用类型提供的类了:

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子地更新一个布尔类型地标记位和引用类型。

AtomicReference 示例

class User{
    private String name;
    public volatile int age;

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

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

class Reference
{
    static AtomicReference<User> atomicUser = new AtomicReference<>();

    public static void main(String[] args) {

        User u = new User("1",10);
        atomicUser.set(u);
        System.out.println(atomicUser.get());
        atomicUser.compareAndSet(u,new User("2",15));
        System.out.println(atomicUser.get());
        System.out.println(atomicUser.compareAndSet(u, new User("3", 123)));
        System.out.println(atomicUser.compareAndSet(new User("2", 15), u));
    }
}

AtomicReferenceFieldUpdate

class AtomicFiled
{
    static AtomicReferenceFieldUpdater<User,String> nameField = AtomicReferenceFieldUpdater.newUpdater(User.class,String.class,"name");

    public static void main(String[] args) {
        //
        User u = new User("123",10);
        System.out.println(u);

        System.out.println(nameField.compareAndSet(u, "123", "xiaohua"));
        System.out.println(u);
        System.out.println(nameField.compareAndSet(u,"123","xiaoli"));
    }
}

AtomicMarkableReference 示例

前面介绍的都是在原子操作下对一个数据进行修改,AtomicMarkableReference 不同的是,它不仅可以修改,还定义了一个变量去判断是他之前是否已经被修改过了,这里就不得不提到ABA问题了:

ABA问题就是如果一个线程把变量a的值由1变成2,另一个线程又把变量a的值由2变回了1,这个时候变量a的值相当于没有变过,但实际上其实已经被更改了,这就是ABA问题。可以举一个更形象的例子,杯子里有一杯水,小明把它喝完了,之后又接满水放回原处,这时小华来了如果知道了杯子被人用过那肯定不会再喝了,如果小明喝完之后那张纸记录下已经用过,那么小华来了就知道了。AtomicMarkableReference就提供了这样一个布尔变量记录值是否被修改过。

AtomicMarkableReference初始化时需要传入一个引用值(类型就是前面填的泛型),此外还需要传入一个布尔值用作判断是否修改。AtomicMarkableReferencecompareAndSet要传入两组参数:旧的引用值和新的引用值;旧的布尔值和新的布尔值,只有传入的旧引用值和旧布尔值与对象中的值相同,才会修改引用值和布尔值。

class AtomicFiled
{

    static AtomicMarkableReference<Integer> intMarkable = new AtomicMarkableReference<>(123,false);

    public static void main(String[] args) {

        System.out.println(intMarkable.getReference());
        System.out.println(intMarkable.isMarked());
        System.out.println(intMarkable.compareAndSet(123,100,false,true));
        System.out.println(intMarkable.getReference());
        System.out.println(intMarkable.isMarked());
        System.out.println(intMarkable.compareAndSet(100,123,false,true));

    }
}

6、原子更新字段类

如果需要原子地更新某个类中的字段时,就需要使用原子更新字段类,Atomic包提供了下面3个类:

  1. AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
  2. AtomicLongFieldUpdater:原子更新长整型的字段的更新器
  3. AtomicStampedReference:原子更新带版本号的引用类型。使用版本号解决ABA问题

需要注意的是,原子地更新字段类需要两步:第一步需要用静态方法newUpdate()创建一个更新器,并且设置想要更新的类和属性。第二步,更新类的字段(属性)必须使用public volatile修饰符。

public class AtomicDemo {
    static AtomicReference<User> atomicUsers = new AtomicReference<>();
    static AtomicIntegerFieldUpdater<User> userAge = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
    static CountDownLatch countDownLatch = new CountDownLatch(2);

  public static void main(String[] args) throws InterruptedException {

          User u = new User("123",0);
          atomicUsers.set(u);
          ExecutorService threadPoolExecutor = new ThreadPoolExecutor(3,
                  6,
                  100,
                  TimeUnit.MILLISECONDS,
                  new ArrayBlockingQueue<Runnable>(10),
                  Executors.defaultThreadFactory(),
                  new ThreadPoolExecutor.AbortPolicy());
          threadPoolExecutor.execute(()->
          {

              try {
                  TimeUnit.MILLISECONDS.sleep(200);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName()+"  "+atomicUsers.get().getAge());
              userAge.incrementAndGet(u);
             countDownLatch.countDown();
          });

          threadPoolExecutor.shutdown();
          countDownLatch.await();
          System.out.println(atomicUsers.get().getAge());
  }
}

到此这篇关于Java多线程 原子操作类详细的文章就介绍到这了,更多相关Java多线程 原子操作类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程Atomic包操作原子变量与原子类详解

    在阅读这篇文章之前,大家可以先看下<Java多线程atomic包介绍及使用方法>,了解atomic包的相关内容. 一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的.通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成) 在x86平台上,CPU提供了在指令执行期间对总线加锁的手段.

  • Java多线程 原子操作类详细

    目录 1.What and Why 2.原子更新基本类型类 3.实现原理 4.原子更新数组 5.原子更新引用类型 6.原子更新字段类 1.What and Why 原子的本意是不能被分割的粒子,而对于一个操作来说,如果它是不可被中断的一个或者一组操作,那么他就是原子操作.显然,原子操作是安全的,因为它不会被打断. 平时我们见到的很多操作看起来是原子操作,但其实是非原子操作,例如很常见的i++操作,它背后有取值.加一.写回等操作,如果有两个线程都要对 i 进行加一操作,就有可能结果把i只变成了2,

  • Java多线程的用法详细介绍

    Java多线程的用法详细介绍 最全面的Java多线程用法解析,如果你对Java的多线程机制并没有深入的研究,那么本文可以帮助你更透彻地理解Java多线程的原理以及使用方法. 1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( ); publi

  • Java中ArrayList类详细介绍

    Java中ArrayList类详细介绍 ArrayList是一个可变长度数组,它实现了List接口,因此它也可以包含重复元素和Null元素,也可以任意的访问和修改元素,随着向 ArrayList 中不断添加元素,其容量也自动增长.不过ArrayList是非同步(同步的意思是如果多个线程同时访问一个实例,任何一个线程对实例做了修改之后,其他线程所访问到的实例应该是修改过的最新的实例)的, 我们经常使用List list = Collections.synchronizedList(new Arra

  • Java多线程Thread类的使用及注意事项

    目录 Thread类的基本用法 线程指标 run和start的区别 中断线程 线程等待 进程状态 线程安全问题 synchronized用法 1.直接修饰普通的方法 2.修饰一个代码块 3.修饰一个静态方法 监视器锁monitor lock 死锁的其他场景 volatile Thread类的基本用法 1.创建子类,继承自Thread并且重写run方法: class MyThread extends Thread { @Override public void run() { System.out

  • Java多线程与优先级详细解读

    目录 1.多线程 1.1多线程的基本概念 1.2多线程的实现 1.3继承Thread类实现多线程 1.4Runnable接口实现多线程 1.5Thread类和Runnable接口实现多线程的区别 1.6线程的操作状态 1.7Callable实现多线程 1.8线程命名和取得 1.9线程的休眠 1.10线程的优先级 1.11线程的同步与死锁 1.12死锁 综合案例 1.解决数据错位问题:依靠同步解决 2.解决数据的重复设置和重复取出 面试题:请解释sleep()和wait()的区别? 1.多线程 要

  • java中dart类详细讲解

    dart 是一个面向对象的语言;面向对象有 继承 封装 多态 dart的所有东西都是对象,所有的对象都是继承与object类 一个类通常是由属性和方法组成的 在dart中如果你要自定义一个类的话,将这个类放在main函数外面 类名使用大驼峰方法名使用小驼峰 1.定义这个类的属性和方法 //定义一个类的属性和方法 class Person { String name = '张三'; int age = 19; void getInfo() { // print('我叫$name,今年$age');

  • Java 反射获取类详细信息的常用方法总结

    类ReflectionDemo 复制代码 代码如下: package Reflection; @Deprecated public class ReflectionDemo {     private String pri_field;     public String pub_field;     public ReflectionDemo(){}     public ReflectionDemo(String name){}     private ReflectionDemo(Stri

  • 浅谈java多线程编程

    一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响应更快 多线程的代价: 1)设计更复杂 虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销 当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据

  • Java Thread多线程开发中Object类详细讲解

    目录 方法概览 Thread wait  notify notifyAll方法详解 作用 阻塞阶段 唤醒阶段 遇到中断 代码展示 特点 通过wait notify方法实现生产者和消费者 sleep方法详解 sleep不会释放锁 sleep响应中断 总结 join方法详解 代码展示 yield方法 方法概览 Thread wait  notify notifyAll方法详解 作用 阻塞阶段 使用了wait方法之后,线程就会进入阻塞阶段,只有发生以下四种情况中的其中一个,线程才会被唤醒 另一个线程调

  • Java多线程 乐观锁和CAS机制详细

    目录 一.悲观锁和乐观锁 1.悲观锁 2.乐观锁 二.CAS机制 一.悲观锁和乐观锁 1.悲观锁 悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作.synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁. 特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对

随机推荐