文档章节

一直使用AtomicInteger?试一试FieldUpdater

字数 1508
阅读 1142
收藏 22

1. 背景

在进入正题之前,这里先提出一个问题,如何在多线程中去对一个数字进行+1操作?这个问题非常简单,哪怕是Java的初学者都能回答上来,使用AtomicXXX,比如有一个int类型的自加,那么你可以使用AtomicInteger 代替int类型进行自加。

 AtomicInteger atomicInteger = new AtomicInteger();
        atomicInteger.addAndGet(1);

如上面的代码所示,使用addAndGet即可保证多线程中相加,具体原理在底层使用的是CAS,这里就不展开细讲。基本上AtomicXXX能满足我们的所有需求,直到前几天一个群友(ID:皮摩)问了我一个问题,他发现在很多开源框架中,例如Netty中的AbstractReferenceCountedByteBuf 类中定义了一个refCntUpdater:

    private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater;

    static {
        AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> updater =
                PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
        if (updater == null) {
            updater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
        }
        refCntUpdater = updater;
    }

refCntUpdater 是Netty用来记录ByteBuf被引用的次数,会出现并发的操作,比如增加一个引用关系,减少一个引用关系,其retain方法,实现了refCntUpdater的自增:

    private ByteBuf retain0(int increment) {
        for (;;) {
            int refCnt = this.refCnt;
            final int nextCnt = refCnt + increment;

            // Ensure we not resurrect (which means the refCnt was 0) and also that we encountered an overflow.
            if (nextCnt <= increment) {
                throw new IllegalReferenceCountException(refCnt, increment);
            }
            if (refCntUpdater.compareAndSet(this, refCnt, nextCnt)) {
                break;
            }
        }
        return this;
    }

俗话说有因必有果,netty多费力气做这些事必然是有自己的原因的,接下来就进入我们的正题。

2.Atomic field updater

java.util.concurrent.atomic包中有很多原子类,比如AtomicInteger,AtomicLong,LongAdder等已经是大家熟知的常用类,在这个包中还有三个类在jdk1.5中都存在了,但是经常被大家忽略,这就是fieldUpdater:

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

这个在代码中不经常会有,但是有时候可以作为性能优化的工具出场,一般在下面两种情况会使用它:

  • 你想通过正常的引用使用volatile的,比如直接在类中调用this.variable,但是你也想时不时的使用一下CAS操作或者原子自增操作,那么你可以使用fieldUpdater。
  • 当你使用AtomicXXX的时候,其引用Atomic的对象有多个的时候,你可以使用fieldUpdater节约内存开销。

2.1 正常引用volatile变量

一般有两种情况需要正常引用:

  1. 当代码中引入已经正常引用,但是这个时候需要新增一个CAS的需求,我们可以将其替换AtomicXXX对象,但是之前的调用都得换成.get().set()方法,这样做会增加不少的工作量,并且还需要大量的回归测试。
  2. 代码更加容易理解,在BufferedInputStream中,有一个buf数组用来表示内部缓冲区,它也是一个volatile数组,在BufferedInputStream中大多数时候只需要正常的使用这个数组缓冲区即可,在一些特殊的情况下,比如close的时候需要使用compareAndSet,我们可以使用AtomicReference,我觉得这样做有点乱,使用fieldUpdater来说更加容易理解,
    protected volatile byte buf[];


    private static final
        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
        AtomicReferenceFieldUpdater.newUpdater
        (BufferedInputStream.class,  byte[].class, "buf");
        
    public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();
                return;
            }
            // Else retry in case a new buf was CASed in fill()
        }
    }

2.2 节约内存

之前说过在很多开源框架中都能看见fieldUpdater的身影,其实大部分的情况都是为了节约内存,为什么其会节约内存呢?

我们首先来看看AtomicInteger类:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
}

在AtomicInteger成员变量只有一个int value,似乎好像并没有多出内存,但是我们的AtomicInteger是一个对象,一个对象的正确计算应该是 对象头 + 数据大小,在64位机器上AtomicInteger对象占用内存如下:

  • 关闭指针压缩: 16(对象头)+4(实例数据)=20不是8的倍数,因此需要对齐填充 16+4+4(padding)=24

  • 开启指针压缩(-XX:+UseCompressedOop): 12+4=16已经是8的倍数了,不需要再padding。

由于我们的AtomicInteger是一个对象,还需要被引用,那么真实的占用为:

  • 关闭指针压缩:24 + 8 = 32
  • 开启指针压缩: 16 + 4 = 20

而fieldUpdater是staic final类型并不会占用我们对象的内存,所以使用fieldUpdater的话可以近似认为只用了4字节,这个再未关闭指针压缩的情况下节约了7倍,关闭的情况下节约了4倍,这个在少量对象的情况下可能不明显,当我们对象有几十万,几百万,或者几千万的时候,节约的可能就是几十M,几百M,甚至几个G。

比如在netty中的AbstractReferenceCountedByteBuf,熟悉netty的同学都知道netty是自己管理内存的,所有的ByteBuf都会继承AbstractReferenceCountedByteBuf,在netty中ByteBuf会被大量的创建,netty使用fieldUpdater用于节约内存。

在阿里开源的数据库连接池druid中也有同样的体现,早在2012的一个pr中,就有优化内存的comment:,在druid中,有很多统计数据对象,这些对象通常会以秒级创建,分钟级创建新的,druid通过fieldUpdater节约了大量内存:

3.最后

AtomicFieldUpdater的确在我们平时使用比较少,但是其也值得我们去了解,有时候在特殊的场景下的确可以作为奇技淫巧。

如果大家觉得这篇文章对你有帮助,你的关注和转发是对我最大的支持,O(∩_∩)O:

© 著作权归作者所有

粉丝 83
博文 25
码字总数 98026
作品 0
东城
私信 提问
加载中

评论(2)

DuLerWeil
DuLerWeil
标题看错错fileuplouder,还在纳闷俩风马牛不相及的东西
AkataMoKa
AkataMoKa
好文!支持!
LinkedBlockingQueue

介绍 LinkedBlockingQueue实现BlockingQueue接口. JDK中位置:java.util.concurrent包下,该包为并发包,同样LinkedBlockingQueue自身实现了并发处理,使用的是存取锁的方式. 自身还实现了读写分...

Adoniscx
2013/12/19
140
0
原子变量与synchronized详细解释

AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加...

cookqq
2013/06/20
368
0
ReentrantReadWriteLock与AtomicInteger的简单应用与对比

背景说明:   模拟春节抢票场景,500个线程(50并发)抢100张票,余票足够则成功取得票,否则不成功。 ReentrantReadWriteLock实现版: public class ReadWriteLockTest { private static fi...

zhengweihao
2015/12/08
127
0
【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?

前言 乐观锁和悲观锁问题,是出现频率比较高的面试题。本文将由浅入深,逐步介绍它们的基本概念、实现方式(含实例)、适用场景,以及可能遇到的面试官追问,希望能够帮助你打动面试官。 一、基...

我最喜欢三大框架
05/29
3
0
LinkedBlockingQueue源码解析(3)

此文已由作者赵计刚授权网易云社区发布。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 4.3、public E take() throws InterruptedException 原理: 将队头元素出队,如果队列空了,一...

网易云
2018/12/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

js—String的一些方法

<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> var str="Hello boy" /** * 在底......

zhengzhixiang
8分钟前
2
0
vSphere ESXi 主机上的3种VLAN设置

VLAN - Virtual Local Area Network,虚拟局域网,能便捷地组建一个网络分组,并能提供诸多好处。VMware vSphere ESXi主机上,也可以在个层次上通过设置VLAN标签地形式来组建VLAN。从划分的层...

大别阿郎
30分钟前
3
0
elasticsearch 6.x的基本dsl语句

本文使用的谷歌浏览器插件sense,链接如下sense插件(兼容es6.x版本) 查看集群状态 http://106.12.27.130:9200/_cat/health?v 绿色-一切都很好(集群功能齐全) 黄色——所有的数据都是可用...

长恭
44分钟前
11
0
移动端的弹窗滚动禁止body滚动

本文转载于:专业的前端网站➼移动端的弹窗滚动禁止body滚动 前言 最近一个需求是弹窗展示列表,显然是需要一个滚动条的,而滚动到底部就会穿透到body滚动,而阻止默认行为是不行的,这样两个...

前端老手
今天
18
0
设计模式 建造者模式和模板方法模式扩展篇

建造者模式和模板方法模式扩展篇 UML 与抽象工厂模式比较 本模式可以看出与抽象工厂非常类似,都是产生不同的产品,怎么区分这两种设计的使用场景呢 - 建造者模式关注的是基本方法的调...

木本本
今天
24
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部