Всегда используете AtomicInteger? Попробуйте FiledUpdater

JavaScript

1. Предпосылки

Прежде чем войти в тему, вот вопрос, как выполнить операцию +1 над числом в несколько потоков? Этот вопрос очень простой, на него сможет ответить даже новичок в Java.Используя AtomicXXX, например, если есть самосложение типа int, то для самосложения можно использовать AtomicInteger вместо типа int.

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

Как показано в приведенном выше коде, использование addAndGet может обеспечить добавление в несколько потоков Конкретным принципом является CAS на нижнем уровне, поэтому я не буду здесь вдаваться в подробности. По сути, AtomicXXX может удовлетворить все наши потребности, пока друг группы (ID: Pymore) не задал мне вопрос несколько дней назад.Он обнаружил, что во многих фреймворках с открытым исходным кодом, таких как класс AbstractReferenceCountedByteBuf в Netty, определен 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. Будут параллельные операции, такие как добавление ссылочной связи и сокращение ссылочной связи. Его метод сохранения реализует самоинкремент 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;
    }

Как говорится, если есть причина, должно быть и следствие. У Нетти должны быть свои причины, чтобы делать эти вещи с большими усилиями. Далее мы перейдем к нашей теме.

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, используемый для представления внутреннего буфера, который также является изменчивым массивом.В BufferedInputStream большую часть времени вам нужно только использовать этот буфер массива в обычном режиме.В некоторых особых случаях, таких как закрытие, вам нужно использовать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(заполнение)=24

  • Включите сжатие указателя (-XX:+UseCompressedOop): 12+4=16 уже кратно 8, заполнение не требуется.

Поскольку наш AtomicInteger является объектом и на него нужно ссылаться, реальное занятие:

  • Отключить сжатие указателя: 24 + 8 = 32
  • Включить сжатие указателя: 16 + 4 = 20

и fieldUpdaterstaic finalТип не занимает память нашего объекта, поэтому если использовать fieldUpdater, то можно примерно сказать, что используется всего 4 байта Это экономит 7 раз, когда сжатие указателя не выключено, и 4 раза, когда оно выключено. Это небольшое количество Это может быть неочевидно в случае объектов Когда у нас есть сотни тысяч, миллионы или десятки миллионов объектов, экономия может составлять десятки M, сотни M или даже несколько G.

Например, AbstractReferenceCountedByteBuf в netty, студенты, знакомые с netty, знают, что netty сама управляет памятью, и все ByteBufs будут наследовать AbstractReferenceCountedByteBuf.В netty ByteBufs будут создаваться в большом количестве, и netty использует fieldUpdater для экономии памяти.

То же самое отражено в druid пула соединений с базой данных с открытым исходным кодом Alibaba.Еще в PR в 2012 году был комментарий об оптимизации памяти:

, в друиде много объектов статистики, эти объекты обычно создаются за секунды, а новые создаются за минуты, друид экономит много памяти через fieldUpdater:

3. Наконец

AtomicFieldUpdater действительно реже используется в нашей повседневной жизни, но и с ним стоит разобраться, а иногда его можно использовать как хитрость в особых сценариях.

Если вы считаете, что эта статья полезна для вас, то ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O:

Эта статья опубликована на многопостовой платформеArtiPubавтоматическая публикация