当前位置: 首页 > article >正文

使用JUC包的AtomicXxxFieldUpdater实现更新的原子性

写在前面

本文一起来看下使用JUC包的AtomicXxxxFieldUpdater实现更新的原子性。代码位置如下:
在这里插入图片描述
当前有针对int,long,ref三种类型的支持。如果你需要其他类型的支持的话,也可以照葫芦画瓢。

1:例子

1.1:普通方式

程序:

package x;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class NormalUpdaterTest {
    CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        Account account = new Account(0);

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Thread t = new Thread(new Task(account));
            list.add(t);
            t.start();
        }

        for (Thread t : list) {
            t.join();
        }

        System.out.println(account.toString());
    }

    private static class Task implements Runnable {
        private Account account;

        Task(Account account) {
            this.account = account;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.increMoney();
            }
        }
    }

    static class Account {
        private volatile int money;

        public Account(int initial) {
            this.money = initial;
        }

        public void increMoney() {
            money++;
        }

        public int getMoney() {
            return money;
        }

        @Override
        public String toString() {
            return "Account{" +
                    "money=" + money +
                    '}';
        }
    }
}

我们期望的结果是100,但结果往往是比100小的,因为整个操作过程并不是线程安全的,根本原因是这个i++它不是原子的,而是三步走,首先拿到i,接着对i+1,最后将i+1的结果写回内存,所以大概率存在盖结果情况发生,运行如下:

Account{money=91}

Process finished with exit code 0

当然这个结果并不是固定不变,因为存在偶然性,但一般都是小于100的。

1.2:原子方式

package x;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class FieldlUpdaterTest {
    CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        Account account = new Account(0);

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Thread t = new Thread(new Task(account));
            list.add(t);
            t.start();
        }

        for (Thread t : list) {
            t.join();
        }

        System.out.println(account.toString());
    }

    private static class Task implements Runnable {
        private Account account;

        Task(Account account) {
            this.account = account;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.increMoney();
            }
        }
    }

    static class Account {
        private volatile int money;
        private AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");

        public Account(int initial) {
            this.money = initial;
        }

        public void increMoney() {
//            money++;
//            fieldUpdater.incrementAndGet(money);
            // 以原子的方式更新this的money
            fieldUpdater.incrementAndGet(this);
        }

        public int getMoney() {
            return money;
        }

        @Override
        public String toString() {
            return "Account{" +
                    "money=" + money +
                    '}';
        }
    }
}

相比于普通方式增加了AtomicIntegerFieldUpdater fieldUpdater专门用来负责更新,更新逻辑也对应的变为fieldUpdater.incrementAndGet(this);,这个时候再运行:

Account{money=100}

Process finished with exit code 0

就对了,因为此时加了cas的乐观锁。

2:源码分析

首先看下代码AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");:

AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                      final String fieldName,
                                      final Class<?> caller) {
    final Field field;
    final int modifiers;
    try {
        ...
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
    ...
    // 获取要更新的类和字段的内存偏移量
    this.cclass = (Modifier.isProtected(modifiers) &&
                   tclass.isAssignableFrom(caller) &&
                   !isSamePackage(tclass, caller))
                  ? caller : tclass;
    this.tclass = tclass;
    this.offset = U.objectFieldOffset(field);
}

代码fieldUpdater.incrementAndGet(this);:

// java.util.concurrent.atomic.AtomicIntegerFieldUpdater.AtomicIntegerFieldUpdaterImpl#getAndAdd
public final int getAndAdd(T obj, int delta) {
    accessCheck(obj);
    // 基于偏移量完成更新,其中getAndAddInt是cas的
    return U.getAndAddInt(obj, offset, delta);
}

// sun.misc.Unsafe#getAndAddIntsun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

注意代码} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));当compare不成功的时候就会返回false,会尝试再次做+1操作,即自旋,直到成功。

3:相比于AtomicXxx的优势

更小的内存占用,为什么呢?比如如下代码:

class Account {
    private volatile int money;
}

如果改造为AtomicInteger的方式则为;

class Account {
    private volatile AtomicInteger money = new AtomicInteger(0);
}

AtomicInteger money的大小在不考虑padding的情况下大概为16字节对象头+4字节的对象内容,即20个字节,那么创建100个对象大小就是2000个字节,但如果是使用AtomicXxxFieldUpdater的代码就如下:

    private volatile int money;
    private static AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");
}

这里我们简单的假定AtomicIntegerFieldUpdater fieldUpdater的大小是24个字节,int money的大小是4个字节。此时我们创建100个对象,money和fieldUpdater占用的大小就是4*100+24,即424,因为fieldUpdater是静态的,所以只有独一份。
可以看到AtomicIntegerFieldUpdater的方式占用了比AtomicInteger少得多的内存,而占用更少的内存,也意味着你的程序不需要申请更多的内存,所以程序执行的速度也会更快。

写在后面

参考文章列表

jvm之对象大小分析。

多线程之JUC 。


http://www.kler.cn/news/366335.html

相关文章:

  • 51单片机记录
  • STM32传感器模块编程实践(十一) ADC模数转换模块ADS1115简介及驱动源码
  • 10-1.idea中的项目结构,辅助快捷键,模块的操作
  • # 渗透测试#安全见闻9 二进制安全
  • steam新品节!GameViewr远程随时随地手机平板玩主机游戏教程
  • hutool常用方法
  • 基于SpringBoot设计模式之结构型设计模式·组合模式
  • Etcd 可观测最佳实践
  • 基于neo4j知识图谱的图书推荐借阅系统
  • 尚硅谷-react教程-求和案例-优化3-整合UI组件和容器组件-总结优化-笔记
  • 机器学习3
  • 【Java】java 集合框架(详解)
  • 高可用开源库之 Hystrix-01-概览
  • FileLink跨网文件交换平台——能源化工行业的安全传输解决方案
  • 近似线性可分支持向量机的原理推导
  • adb常见指令以及问题解决
  • 认识CSS语法
  • YOLOv8_ ByteTrack目标跟踪、模型部署
  • 青少年编程与数学 02-002 Sql Server 数据库应用 06课题、数据库操作
  • 学习笔记——动态路由——OSPF(距离矢量协议)OSPF路由类型
  • 《Windows PE》7.4 资源表应用
  • ES 模块的用法
  • 高效、安全、无忧——P2Link重塑远程访问体验
  • 推送消息存储策略
  • django(3)jinja2模版的使用
  • AJAX—— jQuery 发送 AJAX 请求