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