一、介绍
1.乐观锁 和 悲观锁
乐观锁和悲观锁是广义上的概念,根据是否锁住同步数据来判断。
悲观锁对同一个数据的并发操作,认为自己在使用数据的时候一定有别的线程来修改数据,因此每次操作同步数据时都会先加锁。在java中synchronized关键字和Lock的实现类都是悲观锁。
乐观锁认为自己再使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有更新,当前线程就能更新成功,反之则不行。在java中通过无锁编程来实现。
2.无锁
乐观锁是一个广泛的概念,而无锁便是乐观锁在java中的具体实现。
无锁不对资源进行锁定,所有线程都能访问并修改同一个资源,但是同时只有一个线程能修改成功。
无锁采用的是CAS(Compare And Swap 比较并交换),并不断循环尝试。
3.CAS
CAS涉及到三个操作数:
① 需要读写的内存值V
② 进行比较的值A
③ 需要写入的新值B
通过处理器的原子指令(cmpxchg)可以实现比较V=A并且B更新V。
在java.util.concurrent包中的各种原子类就是通过CAS来实现乐观锁的。
CAS虽然高效但是它存在三大问题:
① ABA问题
② 循环时间开销大,如果竞争激烈重试频繁发生,反而效率会受到影响
③ 只能保证一个共享变量的原子操作
二、Atomic
1.介绍
从jdk1.5以来,JDK并发包中提供了一些类来支持原子操作,它们属于乐观锁,通过cas实现。
2.原子整数
① AtomicInterger
② AtomicLong
③ AtomicBoolean
底层是调用unsafe类的cas api
AtomicInterger i = new AtomicInterger(0);
i.compareAndSet()
i.incrementAndGet(); // ++i
i.getAndIncrement(); // i++
i.getAndAdd(x); // x++
i.addAndGet(x); // ++X
i.updateAndGet(x -> x*10);
i.get() // 获取值
2.原子引用
① AtomicReference
② AtomicMarkableReference
③ AtomicStampedReference
需要注意的是AtomicReference比较的引用地址是否发生改变。这点用voliate修饰引用类型一样,这能保证引用类型地址发生改变时的可见性,不能保证引用类型属性发生改变的可见性。
AtomicReference<T> test = new AtomicReference<>(T t);
T t1 = test.get();
test .compareAndSet() // 返回值Boolean类型
之前提过CAS存在ABA问题,解决ABA的方法就是加个版本号。
而AtomicStampedReference就可以携带版本号
AtomicStampedReference<T t> test = AtomicStampedReference<>(T t,0) // 0就是版本号
T t1 = test.getReference(); // 获得原引用
int stamp = test.getStamp(); // 获得版本号
test.compareAndSet(t1,T newt,stamp,stamp + 1);
3.原子数组
① AtomicIntegerArray
② AtomicLongArray
③ AtomicReferenceArray
AtomicIntergerArray test = new AtomicIntergerArray(10); // 等同于创建一个长度为10的数组
test.getAndIncrement(index) // 数组对应下标值加1
4.字段更新器
① AtomicReferenceFieldUpdater
② AtomicIntegerFieldUpdater
③ AtomicLongFieldUpdater
AtomicReferenceFieldUpdater test = AtomicReferenceFieldUpdater.newUpdater(Student.class,T.class,"字段名称");
test .compareAndSet(T t,字段原值,字段新值);
5.原子累加器
jdk1.8出现了几个专门用来做累加的类,这些类比atomic类做累加效率要高。
new LongAdder();
adder.increment();