javaCASAtomic
MR.XSSCAS及原子操作
概述
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。CAS也是现在面试经常问的问题,本文将深入的介绍CAS的原理。
CAS与volatile
结合CAS和volatile可以实现无锁并发,适用于线程数少、多核CPU的场景下使用
volatile
获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。
它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是操作主存。即一个线程对volatile变量的修改,对另一个线程可见。
CAS必须借助volatile才能读取到共享变量的最新值来实现Compare-and-Swap的效果。
特点
优点:
缺点:
无法解决ABA问题
可能会消耗较高的CPU
不能保证代码块的原子性
乐观锁
乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);
特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。
悲观锁
顾名思义,悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;
特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;
原子包下的类
在java.util.concerrent.atomic包下面的类
原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).
原子整数
AtomicBoolean
AtomicInteger
AtomicLong
API
原子引用
简介
JUC提供如下原子引用类:
AtomicReference
:普通原子引用类型,对对象进行原子操作
AtomicStampedReference
:带int类型版本戳的原子引用类型,记录更改次数
AtomicMarkableReference
:带boolean类型版本戳的原子引用类型,记录是否更改
作用:保证引用类型的共享变量是线程安全的。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class DecimalAccountCAS implements DecimalAccount { private AtomicReference<BigDecimal> balance;
public DecimalAccountCAS(BigDecimal balance) { this.balance = new AtomicReference<>(balance); }
@Override public BigDecimal getBalance() { return balance.get(); }
@Override public void withdraw(BigDecimal amount) { while (true) { BigDecimal prev = balance.get(); BigDecimal next = prev.subtract(amount); if (balance.compareAndSet(prev, next)) { break; } } } }
interface DecimalAccount { BigDecimal getBalance(); void withdraw(BigDecimal amount); }
|
ABA问题
ABA问题是,当一个线程对数据进行修改时为B时,先检查原来数据是不是A,而在检查期间,另外一个线程修改数据为B,然后对数据又修改为A的情况,这种情况为ABA为问题
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package Thread.Day05;
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicStampedReference;
public class Demo02 { static AtomicStampedReference<String> reference = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException { int stamp = reference.getStamp(); other(); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"-----------操作-----------"); System.out.println("操作前版本号----->"+stamp); boolean flag = reference.compareAndSet(reference.getReference(), "B", stamp, stamp+1); System.out.println("操作后版本号是------->"+reference.getStamp()+"操作:"+flag); }
public static void other() { new Thread(() -> { System.out.println(Thread.currentThread().getName()+"-----------操作-----------"); int stamp = reference.getStamp(); System.out.println("操作前版本号----->"+stamp); boolean flag = reference.compareAndSet(reference.getReference(), "B", stamp, stamp+1); System.out.println("操作后版本号是------->"+reference.getStamp()+"操作:"+flag); }).start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { System.out.println(Thread.currentThread().getName()+"-----------操作-----------"); int stamp = reference.getStamp(); System.out.println("操作前版本号----->"+stamp); boolean flag = reference.compareAndSet(reference.getReference(), "A", stamp, stamp+1); System.out.println("操作后版本号是------->"+reference.getStamp()+"操作:"+flag); }).start(); } }
|
原子数组
JUC提供如下原子数组类:
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
作用:保证数组内的元素的线程安全。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @Slf4j(topic = "AtomicArray") public class AtomicArrayDemo { public static void main(String[] args) { demo( () -> new int[10], array -> array.length, (array, index) -> array[index]++, array -> log.debug("普通数组:{}", Arrays.toString(array)) ); demo( () -> new AtomicIntegerArray(10), AtomicIntegerArray::length, AtomicIntegerArray::getAndIncrement, array -> log.debug("安全数组:{}", array) ); }
private static <T> void demo(Supplier<T> arraySupplier, Function<T, Integer> lengthFunction, BiConsumer<T, Integer> putConsumer, Consumer<T> printConsumer) { List<Thread> list = new ArrayList<>(); T array = arraySupplier.get(); int length = lengthFunction.apply(array);
for (int i = 0; i < length; i++) { list.add(new Thread(() -> { for (int j = 0; j < 10000; j++) { putConsumer.accept(array, j % length); } })); }
list.forEach(Thread::start); list.forEach(t -> { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); printConsumer.accept(array); } }
|
字段更新器
JUC提供如下字段更新器:
AtomicReferenceFeildUpdater
:引用类型的属性
AtomicIntegerFieldUpdater
:整形的属性
AtomicLongFeildUpdater
:长整形的属性
注意:利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合volatile修饰的字段使用,否则会出现异常。
1
| Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Slf4j(topic = "AtomicFieldUpdater") public class AtomicFieldUpdaterDemo { public static void main(String[] args) { Student stu = new Student(); AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name"); log.debug("update ? {}", updater.compareAndSet(stu, null, "RubbishK")); log.debug("update ? {}", updater.compareAndSet(stu, stu.getName(), "FlowerK")); log.debug(stu.toString()); } }
class Student { volatile String name;
public String getName() { return name; }
@Override public String toString() { return "Student[name='" + name + "']"; } }
|
CAS锁
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package Thread.Day05;
import java.util.concurrent.atomic.AtomicInteger;
public class CAS_Lock { private AtomicInteger LOCK_STATE = new AtomicInteger(0);
public void setLock() { while(true) if (LOCK_STATE.compareAndSet(0, 1)) break; }
public void unLock() { LOCK_STATE.compareAndSet(1, 0); }
}
|