无锁优化、自旋锁、乐观锁
首先要明确一点,自旋而”无锁”并不一定就比有锁快,因为自旋是在占用CPU,如果很多个线程一起自旋很久,想想都觉得效率很低…具体情况还是要具体分析.
概念 Compare And Swap/Set
下面是一段说明CAS原理的伪代码:
/*** nowValue:当前值,这个值是随时可能被修改的* expectedValue:期望值,在调用casMethod前获取到的nowValue* newValue:想要修改的新的值*/casMethod(nowValue, expectedValue, newValue){// 如果当前值和期望值相等,说明本次修改期间没有其他线程修改,则赋值if(nowValue == expectedValue) {// 问题:此时,已经判定为其他线程没有修改,那么在赋值前会不会被其他线程修改了?// 答:不会,cas操作是CPU原语支持,是CPU指令指令级别上的支持,中间不能被打断nowValue = newValue;} else {// 如果当前值和期望值不等,说明本次修改期间有其他线程已经修改了值,// 那么就再试一次或者直接返回修改失败的结果// todo try again or return fail}
应用:AtomicInteger等atomic类
简单使用
下面这段代码,用了AtomicInteger后,自增部分(count++)不需要加同步锁,输出结果为100000
public class T01_AtomicInteger {/*volatile*/ //int count1 = 0;AtomicInteger count = new AtomicInteger(0);/*synchronized*/ void m() {for (int i = 0; i < 10000; i++){//if count1.get() < 1000count.incrementAndGet(); //count1++}}public static void main(String[] args) {T01_AtomicInteger t = new T01_AtomicInteger();List<Thread> threads = new ArrayList<Thread>();for (int i = 0; i < 10; i++) {threads.add(new Thread(t::m, "thread-" + i));}threads.forEach((o) -> o.start());threads.forEach((o) -> {try {o.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(t.count);}}
简单的分析一波incrementAndGet()
跟踪下去,count.incrementAndGet():
/*** Atomically increments by one the current value.** @return the updated value*/public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}
继续跟踪,到了Unsafe.class,这里compareAndSwapInt就是用到了CAS:
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}
Unsafe.class比较复杂,直接操作JVM中的内存,类似C和C++的操作,比如分配一个对象不用new,而是直接写在内存中;操作对象的属性也可以根据”地址”(or指针)和偏移量定位.
ABA问题
什么是ABA?
在开始使用CAS修改一个值后,在CAS中判断nowValue == expectedValue前,假设有一个线程先把nowValue改成了其他值x,然后又把x改回了nowValue,那么这时候虽然nowValue等于expectedValue,但这个值其实已经被修改过了.
ABA会带来什么问题?
如果是AtomicInteger等数值类型,其实ABA是没有影响的,无所谓.
如果是一个对象引用,多数情况是不允许ABA情况的(比如小黄和其女朋友小白要结婚了,但是突然来了小黑抢走了小白,他们成为恋人,过了段时间又把小白还了回来,这婚多半就结不成了).
怎么避免ABA?
加上版本号version,做任何操作时都把version+1,同时比较nowValue和version
假设nowValue值为A,version为1,如果有ABA情况发生,即nowValue值变为B后又变回A,那么此时version是3,就可以根据version知道值已经被修改过了.
例如java.util.concurrent.atomic.AtomicStampedReference
Atomic的常见问题
高并发实现递增的三种方式?
同步static long count2 = 0L;
synchronized (lock) {
count2++;
}
CAS原子操作AtomicLong count1 = new AtomicLong(0L);
count1.incrementAndGet();
分段锁LongAdder count3 = new LongAdder();
count3.increment();
下面代码是一个简单的测试(1000个线程,累加10W次),从中可以LongAdder最快,AtomicLong次之,synchronize最慢.
synchronize慢是因为会升级成重量级锁,向OS申请资源加锁.
注意:如果线程数较少,或者累加次数较少,LongAdder比AtomicLong慢.所以实际项目中,还是要看项目中的并发度如何.
public class T02_AtomicVsSyncVsLongAdder {static long count2 = 0L;static AtomicLong count1 = new AtomicLong(0L);static LongAdder count3 = new LongAdder();public static void main(String[] args) throws Exception {Thread[] threads = new Thread[1000];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(() -> {for (int k = 0; k < 100000; k++) {count1.incrementAndGet();}});}long start = System.currentTimeMillis();for (Thread t : threads) {t.start();}for (Thread t : threads) {t.join();}long end = System.currentTimeMillis();//TimeUnit.SECONDS.sleep(10);System.out.println("Atomic: " + count1.get() + " time " + (end - start));//-----------------------------------------------------------Object lock = new Object();for (int i = 0; i < threads.length; i++) {threads[i] =new Thread(new Runnable() {@Overridepublic void run() {for (int k = 0; k < 100000; k++) {synchronized (lock) {count2++;}}}});}start = System.currentTimeMillis();for (Thread t : threads) {t.start();}for (Thread t : threads) {t.join();}end = System.currentTimeMillis();System.out.println("Sync: " + count2 + " time " + (end - start));//----------------------------------for (int i = 0; i < threads.length; i++) {threads[i] =new Thread(() -> {for (int k = 0; k < 100000; k++) {count3.increment();}});}start = System.currentTimeMillis();for (Thread t : threads) {t.start();}for (Thread t : threads) {t.join();}end = System.currentTimeMillis();//TimeUnit.SECONDS.sleep(10);System.out.println("LongAdder: " + count1.longValue() + " time " + (end - start));}}
为什么高并发下,LongAdder比AtomicLong快?
LongAdder内部实现类似”分段锁”(分段锁也是CAS操作),把值放在数组里,每个元素作为一个相对独立的部分,分散开线程的压力,最后再汇总起来.(有点类似于MapReduce思想)
参考
语雀:从CAS到手写原子类
https://www.yuque.com/humingming/gmztzt/pvgp83
