无锁优化、自旋锁、乐观锁
首先要明确一点,自旋而”无锁”并不一定就比有锁快,因为自旋是在占用CPU,如果很多个线程一起自旋很久,想想都觉得效率很低…具体情况还是要具体分析.

概念 Compare And Swap/Set

下面是一段说明CAS原理的伪代码:

  1. /**
  2. * nowValue:当前值,这个值是随时可能被修改的
  3. * expectedValue:期望值,在调用casMethod前获取到的nowValue
  4. * newValue:想要修改的新的值
  5. */
  6. casMethod(nowValue, expectedValue, newValue){
  7. // 如果当前值和期望值相等,说明本次修改期间没有其他线程修改,则赋值
  8. if(nowValue == expectedValue) {
  9. // 问题:此时,已经判定为其他线程没有修改,那么在赋值前会不会被其他线程修改了?
  10. // 答:不会,cas操作是CPU原语支持,是CPU指令指令级别上的支持,中间不能被打断
  11. nowValue = newValue;
  12. } else {
  13. // 如果当前值和期望值不等,说明本次修改期间有其他线程已经修改了值,
  14. // 那么就再试一次或者直接返回修改失败的结果
  15. // todo try again or return fail
  16. }

应用:AtomicInteger等atomic类

简单使用

下面这段代码,用了AtomicInteger后,自增部分(count++)不需要加同步锁,输出结果为100000

  1. public class T01_AtomicInteger {
  2. /*volatile*/ //int count1 = 0;
  3. AtomicInteger count = new AtomicInteger(0);
  4. /*synchronized*/ void m() {
  5. for (int i = 0; i < 10000; i++){
  6. //if count1.get() < 1000
  7. count.incrementAndGet(); //count1++
  8. }
  9. }
  10. public static void main(String[] args) {
  11. T01_AtomicInteger t = new T01_AtomicInteger();
  12. List<Thread> threads = new ArrayList<Thread>();
  13. for (int i = 0; i < 10; i++) {
  14. threads.add(new Thread(t::m, "thread-" + i));
  15. }
  16. threads.forEach((o) -> o.start());
  17. threads.forEach((o) -> {
  18. try {
  19. o.join();
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. });
  24. System.out.println(t.count);
  25. }
  26. }

简单的分析一波incrementAndGet()

跟踪下去,count.incrementAndGet():

  1. /**
  2. * Atomically increments by one the current value.
  3. *
  4. * @return the updated value
  5. */
  6. public final int incrementAndGet() {
  7. return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  8. }

继续跟踪,到了Unsafe.class,这里compareAndSwapInt就是用到了CAS:

  1. public final int getAndAddInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  6. return var5;
  7. }

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慢.所以实际项目中,还是要看项目中的并发度如何.

  1. public class T02_AtomicVsSyncVsLongAdder {
  2. static long count2 = 0L;
  3. static AtomicLong count1 = new AtomicLong(0L);
  4. static LongAdder count3 = new LongAdder();
  5. public static void main(String[] args) throws Exception {
  6. Thread[] threads = new Thread[1000];
  7. for (int i = 0; i < threads.length; i++) {
  8. threads[i] = new Thread(() -> {
  9. for (int k = 0; k < 100000; k++) {
  10. count1.incrementAndGet();
  11. }
  12. });
  13. }
  14. long start = System.currentTimeMillis();
  15. for (Thread t : threads) {
  16. t.start();
  17. }
  18. for (Thread t : threads) {
  19. t.join();
  20. }
  21. long end = System.currentTimeMillis();
  22. //TimeUnit.SECONDS.sleep(10);
  23. System.out.println("Atomic: " + count1.get() + " time " + (end - start));
  24. //-----------------------------------------------------------
  25. Object lock = new Object();
  26. for (int i = 0; i < threads.length; i++) {
  27. threads[i] =
  28. new Thread(new Runnable() {
  29. @Override
  30. public void run() {
  31. for (int k = 0; k < 100000; k++) {
  32. synchronized (lock) {
  33. count2++;
  34. }
  35. }
  36. }
  37. });
  38. }
  39. start = System.currentTimeMillis();
  40. for (Thread t : threads) {
  41. t.start();
  42. }
  43. for (Thread t : threads) {
  44. t.join();
  45. }
  46. end = System.currentTimeMillis();
  47. System.out.println("Sync: " + count2 + " time " + (end - start));
  48. //----------------------------------
  49. for (int i = 0; i < threads.length; i++) {
  50. threads[i] =
  51. new Thread(() -> {
  52. for (int k = 0; k < 100000; k++) {
  53. count3.increment();
  54. }
  55. });
  56. }
  57. start = System.currentTimeMillis();
  58. for (Thread t : threads) {
  59. t.start();
  60. }
  61. for (Thread t : threads) {
  62. t.join();
  63. }
  64. end = System.currentTimeMillis();
  65. //TimeUnit.SECONDS.sleep(10);
  66. System.out.println("LongAdder: " + count1.longValue() + " time " + (end - start));
  67. }
  68. }

为什么高并发下,LongAdder比AtomicLong快?

LongAdder内部实现类似”分段锁”(分段锁也是CAS操作),把值放在数组里,每个元素作为一个相对独立的部分,分散开线程的压力,最后再汇总起来.(有点类似于MapReduce思想)

参考

语雀:从CAS到手写原子类
https://www.yuque.com/humingming/gmztzt/pvgp83