Параллелизм Java — неблокирующая синхронизация
Введение в проблему CAS
При проблемах параллелизма первое, что приходит на ум, несомненно, синхронизация с взаимоисключающими блокировками, но блокировка потоков и пробуждение приносят большие проблемы с производительностью. всегда есть такая конкуренция.
Что такое КАС
CAS, сравнить и обменять (Compare-and-Swap, CAS), если ожидаемое значение совпадает со значением в основной памяти, то заменить значение, которое нужно обновить, также известное как оптимистическая блокировка.
Если поток A копирует переменную A из основной памяти в 1 и изменяет копию A на 10 в своем собственном потоке, когда поток A готовится обновить эту переменную в основную память, если значение основной памяти A не изменяется (ожидаемое значение), или 1, то поток A успешно обновляет значение A в основной памяти. Но если значение основной памяти A было изменено другими потоками первым и не равно 1, то поток A продолжает повторять попытки до тех пор, пока не добьется успеха (spin).
откуда КАС
КАС принадлежитJ.U.Cпакет, называемыйUnsafeМетод в классе, который представляет собой атомарную операцию с аппаратной поддержкой, которую нельзя прервать или остановить без синхронизации взаимного исключения.
Возьмите метод getAndAddInt в AtomicInteger в качестве примера, U — это класс Unsafe.
/**
* @param expectedValue 期望值
* @param newValue 新值
* @return 比价更新是否成功.
*/
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
Глядя дальше, получите предыдущее значение v через getIntVolatile(o, offset), вызвавweakCompareAndSetInt()Чтобы выполнить сравнение CAS, если значение в адресе памяти этого поля равно v, тогда обновите переменную, адрес памяти которой равен o+offset, на v + delta. Метод getAndAddInt() выполняется в цикле,Практика конфликта состоит в том, чтобы продолжать повторять попытки.
/**
* @param o 更新字段/元素的对象/数组
* @param offset 字段/元素偏移量
* @param delta 要添加的值,步长
* @return 以前的值
* @since 1.8
*/
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
Запустите пример кода
Потоки t1, t2 одновременно изменяют значение переменной в основной памяти, искусственно делая B быстрее, чем A
public class TestCAS {
// 主内存atomicInteger初始值为1
public static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
// A线程计划将值改为10,先休眠2s,再比较交换
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程: "+Thread.currentThread().getName()+"比较交换结果:"
+atomicInteger.compareAndSet(1, 10)+" 现在值为:"+atomicInteger.get());
},"t1").start();
// B线程计划将值改为10,不休眠
new Thread(() -> {
System.out.println("当前线程: "+Thread.currentThread().getName()+"比较交换结果:"
+atomicInteger.compareAndSet(1, 20)+" 现在值为:"+atomicInteger.get());
},"t2").start();
}
}
консоль
当前线程: t2比较交换结果:true 现在值为:20
当前线程: t1比较交换结果:false 现在值为:20
Таким образом, нет необходимости добавлять блокировки синхронизации, и решаются проблемы, связанные с одновременным изменением переменных.
Если ваш хороший друг одолжил у вас 10 юаней и дал вам 10 юаней на следующий день, если ваш друг только что купил пакет закусок, вам может быть все равно, как он использовал джекпот в 10 юаней, возможно, вы торопитесь ...
Введение в проблему ABA
В предыдущем коде есть проблема. Например: потоки t1, t2 копируются в переменную atomicInteger=1, если поток B имеет более высокий приоритет или повезло, первый раз, t2 сначала меняет atomicInteger на 20 и успешно записывает его в основную память, а затем t2 копируется в atomicInteger= 20, снова изменяет копию на 1 и успешно записывает обратно в основную память. В третий раз t1 получает значение atomicInteger в основной памяти. Но это значение было дважды изменено t2, не будет ли проблем?
АВА-проблема
Если переменная была сначала прочитана со значением A, ее значение было изменено на B, а затем изменено обратно на A, операция CAS ошибочно приняла бы это за тот факт, что оно никогда не изменялось.
АВА-решение
-
Блокировка синхронизации взаимного исключения синхронизирована
-
Если проект заботит только правильность значений, то проблема ABA не влияет на корректность параллелизма программ.
-
Пакет J.U.C предоставляет атомарный ссылочный класс с метками времени.
AtomicStampedReferenceрешить проблему путемверсия управляющей переменнойдля обеспечения правильности CAS.
Пример кода AtomicStampedReference
public class SolveCAS {
// 主内存共享变量,初始值为1,版本号为1
private static AtomicStampedReference<Integer> atomicStampedReference = new
AtomicStampedReference<>(1, 1);
public static void main(String[] args) {
// t1,期望将1改为10
new Thread(() -> {
// 第一次拿到的时间戳
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+" 第1次时间戳:"+stamp+" 值为:"+atomicStampedReference.getReference());
// 休眠5s,确保t2执行完ABA操作
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
// t2将时间戳改为了3,cas失败
boolean b = atomicStampedReference.compareAndSet(1, 10, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+" CAS是否成功:"+b);
System.out.println(Thread.currentThread().getName()+" 当前最新时间戳:"+atomicStampedReference.getStamp()+" 最新值为:"+atomicStampedReference.getReference());
},"t1").start();
// t2进行ABA操作
new Thread(() -> {
// 第一次拿到的时间戳
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+" 第1次时间戳:"+stamp+" 值为:"+atomicStampedReference.getReference());
// 休眠,修改前确保t1也拿到同样的副本,初始值为1
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
// 将副本改为20,再写入,紧接着又改为1,写入,每次提升一个时间戳,中间t1没介入
atomicStampedReference.compareAndSet(1, 20, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+" 第2次时间戳:"+atomicStampedReference.getStamp()+" 值为:"+atomicStampedReference.getReference());
atomicStampedReference.compareAndSet(20, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName()+" 第3次时间戳:"+atomicStampedReference.getStamp()+" 值为:"+atomicStampedReference.getReference());
},"t2").start();
}
}
консоль
t1 第1次时间戳:1 值为:1
t2 第1次时间戳:1 值为:1
t2 第2次时间戳:2 值为:20
t2 第3次时间戳:3 值为:1
t1 CAS是否成功:false
t1 当前最新时间戳:3 最新值为:1