(Базовая серия) использование, принцип и цель класса атомарных переменных

Java задняя часть JVM Операционная система

предисловие

В нынешнюю эпоху стремительного технологического развития быстрое развитие компьютеров уже превзошло «закон Мура». В эту эпоху относительно дешевых компьютеров машины, на которых работают разработчики, уже не являются одноядерными процессорами, а уже вступили в эпоху многоядерных процессоров, а предприятия уже вступили в параллельную работу; навыки, необходимые для разработки программ с высокой степенью параллелизма, уже не нужны. более неэффективно использовать Lock, к счастью, jdk1.5 обеспечивает безблокировочные атомарные операции в случае многопоточности, о чем и будет написана эта статья.

Что такое «атомарный класс переменных»?

Начиная с JDK1.5 предоставляется пакет java.util.concurrent.atomic, который удобен программистам для выполнения атомарных операций без блокировок в многопоточной среде. Нижний уровень атомарных переменных использует атомарные инструкции, предоставляемые процессором, но разные архитектуры ЦП могут предоставлять разные атомарные инструкции, а также могут требовать некоторой формы внутренней блокировки, поэтому этот метод не может абсолютно гарантировать, что поток не будет заблокирован. -В общем, он обеспечивает неблокирующее потокобезопасное программирование.

Простое использование классов атомарных переменных

Прежде чем представить использование, давайте разберемся, какие атомарные классы и методы предоставляются нам в пакете jdk java.util.concurrent.atomic:

(1) Резюме класса

своего рода описывать
AtomicBoolean Логическое значение, которое может быть обновлено атомарно.
AtomicInteger Значение int, которое может быть обновлено атомарно.
AtomicIntegerArray Массив int, элементы которого можно обновлять атомарно.
AtomicIntegerFieldUpdater Утилита на основе отражения, которая позволяет атомарно обновлять указанные поля volatile int указанных классов.
AtomicLong Длинное значение, которое может быть обновлено атомарно.
AtomicLongArray Массив longs, элементы которого могут быть обновлены атомарно.
AtomicLongFieldUpdater Утилита на основе отражения, которая позволяет выполнять атомарные обновления указанных изменчивых длинных полей указанных классов.
AtomicMarkableReference AtomicMarkableReference поддерживает ссылку на объект с помеченными битами, которые можно обновлять атомарно.
AtomicReference Ссылка на объект, которая может быть обновлена ​​атомарно.
AtomicReferenceArray Массив ссылок на объекты, элементы которого можно обновлять атомарно.
AtomicReferenceFieldUpdater Утилита на основе отражения, которая позволяет выполнять атомарные обновления указанных изменчивых полей указанных классов.
AtomicStampedReference AtomicStampedReference поддерживает ссылку на объект с целочисленным «штампом», который может быть обновлен атомарно.

(2) Краткое изложение общих методов

возвращаемый тип метод описывать
boolean compareAndSet(boolean expect, boolean update) Атомарно устанавливает значение в заданное обновленное значение, если текущее значение == ожидаемое значение
boolean get() Возвращает текущее значение.
void set(boolean newValue) Безоговорочно устанавливается на заданное значение.
boolean weakCompareAndSet(boolean expect, boolean update) Атомарно устанавливает значение в заданное обновленное значение, если текущее значение == ожидаемому значению.

Здесь перечислены только наиболее часто используемые методы, которые на практике немного различаются в зависимости от атомарных классов. Для получения дополнительной информации, пожалуйста, проверьте "онлайн-документация-jdk-z"

Онлайн-документация-jdk-ru

(3) Простой пример использования

Пример 1: класс базового типа атомарного обновления — создание серийного номера

public class Example1 {

    private final AtomicLong sequenceNumber = new AtomicLong(0);
    public long next() {
        //原子增量方法,执行的是i++,所以需要在获取一次。
        sequenceNumber.getAndIncrement();
        return sequenceNumber.get();
    }

    public void radixNext(int radix){
        for (;;) {
            long i = sequenceNumber.get();
            // 该方法不一定执行成功,所以用无限循环来保证操作始终会执行成功一次。
            boolean suc = sequenceNumber.compareAndSet(i, i + radix);
            if (suc) {
                break;
            }
        }
    }


    public static void main(String[] args) {
        Example1 sequencer = new Example1();

        //生成序列号
        for (int i = 0; i < 10; i++) {
            System.out.println(sequencer.next());
        }

        //生成自定义序列号
        for (int i = 0; i < 10; i++) {
            sequencer.radixNext(3);
            System.out.println(sequencer.sequenceNumber.get());
        }


    }

}

Результаты:

1
2
3
4
5
---------------
8
11
14
17
20

Пример 2: атомарное обновление массива

public class Example2 {

    static AtomicIntegerArray arr = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable{
        public void run(){
            for(int k=0;k<10000;k++){
                // 以原子方式将索引 i 的元素加 1。
                arr.getAndIncrement(k%arr.length());
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[]ts=new Thread[10];
        //创建10个线程
        for(int k=0;k<10;k++){
            ts[k] = new Thread(new AddThread());
        }

        //开启10个线程
        for(int k=0;k<10;k++){
            ts[k].start();
        }

        //等待所有线程执行完成
        for(int k=0;k<10;k++){
            ts[k].join();
        }

        //打印最终执行结果
        System.out.println(arr);
    }
}

Результаты:

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

Пример 3: атомарное обновление ссылок

public class Node {
    private int val;
    private volatile Node left, right;

    private static final AtomicReferenceFieldUpdater leftUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
    private static AtomicReferenceFieldUpdater rightUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");

    boolean compareAndSetLeft(Node expect, Node update) {
        return leftUpdater.compareAndSet(this, expect, update);
    }

    public Node() {
        this.left = this.right = null;
    }

    public Node(int val) {
        this.val = val;
        this.left = this.right = null;
    }

    public Node(Node left,Node right) {
        this.left = left;
        this.right = right;
    }


    public static void main(String[] args) {
        Node node = new Node(1);
        node.left = new Node(new Node(2),new Node(3));
        node.right = new Node(new Node(4),new Node(5));
        System.out.println(JSON.toJSON(node));
        node.compareAndSetLeft(node.left,node.right);
        System.out.println(JSON.toJSON(node));
    }



    // get and set ...

}

Результаты:

{"val":1,"left":{"val":0,"left":{"val":2},"right":{"val":3}},"right":{"val":0,"left":{"val":4},"right":{"val":5}}}
{"val":1,"left":{"val":0,"left":{"val":4},"right":{"val":5}},"right":{"val":0,"left":{"val":4},"right":{"val":5}}}

(4) Резюме

Эффекты памяти атомарного доступа и обновлений обычно следуют объявлениям в следующих изменяемых правилах:

  • get имеет эффект памяти при чтении изменчивой переменной.
  • set имеет эффект памяти записи (выделения) изменчивой переменной.
    В дополнение к разрешению использования последующих (но не предыдущих) операций с памятью, которые сами по себе не налагают ограничений на переупорядочение при обычной энергонезависимой записи, lazySet имеет эффект памяти при записи (выделении) энергозависимых переменных. В других контекстах использования, когда значение null (для сборки мусора), lazySet может применять ссылки, к которым больше не будет доступа.
  • weakCompareAndSet атомарно считывает и условно записывает переменные, но не создает никакого упорядочения «происходит до» и, следовательно, не дает никаких гарантий в отношении предыдущих или последующих операций чтения или записи любой переменной, кроме цели weakCompareAndSet.
  • compareAndSet и все другие операции чтения и обновления (например, getAndIncrement) имеют эффект памяти при чтении и записи изменчивых переменных.

Как работают атомарные операции

Исходный код ключа:

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

Глядя на исходный код, можно обнаружить, что классы в пакете Atomic в основном реализованы с использованием Unsafe, а Unsafe предоставляет только следующие три метода CAS.

Исходный код ключа:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

Несложно обнаружить, что метод модифицирован нативным, то есть модифицированный нативом метод указывает на конкретную реализацию не-java кода при его вызове, и эта реализация может быть на других языках или операционных системах. Это достигается вызовом инструкций нижнего уровня ЦП с помощью C. Чтобы узнать конкретный принцип реализации, нажмите «принцип реализации» ниже.

Принцип реализации

Назначение атомарных объектов

Классы атомарных переменных в основном используются в качестве различных строительных блоков для реализации неблокирующих структур данных и связанных классов инфраструктуры. Метод compareAndSet не является универсальной заменой блокировок. Он применяется только тогда, когда значительные обновления объекта ограничиваются одной переменной.

Пример: Многопоточный счетчик с высоким уровнем параллелизма

Резюме (взято из интернета)

Классы атомарных переменных имеют несколько преимуществ в производительности по сравнению с версией на основе блокировки. Во-первых, он заменяет путь кода блокировки JVM собственной формой аппаратного обеспечения, обеспечивая синхронизацию на более тонком уровне детализации (независимые области памяти), а неудачные потоки также могут быть немедленно повторены без приостановки, а затем перепланированы. Более точная детализация снижает вероятность конфликтов, а возможность повторных попыток без повторного планирования также снижает стоимость конфликтов. Даже при небольшом количестве неудачных операций CAS этот подход все равно будет намного быстрее, чем перепланирование из-за конфликта блокировок.