开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
我们先来讲下乐观锁跟悲观锁
悲观锁:我现在要操作一个共享数据,我认为这个共享数据在我操作的时候,我很悲观的认为别人也会来操作,为了不让别人来操作,我就会给这个共享的数据加上一个同步锁,比如synchronized,在这个期间只能我来操作,别人都不能操作,直到我把锁释放
乐观锁:我们操作一个共享数据,认为这个数据数据不会被别人修改,我先在内存修改,修改完之后要把变量的值同步到最新的,此时要对比下这个值是不是跟我修改之前看到的是一样的,如果是一样的就修改,不一样就重新基于修改逻辑进行修改,然后在进行对比,直到这个值跟我修改之前看到的是一样的为止,然后修改。
CAS(Compare and Swap),从字面上看就是比较跟交换的意思,JUC下边的类借助CAS实现了乐观锁。
CAS会操作3个数字,当前内存中的值,旧的预期值,新的修改值,只有当旧的预期值跟内存中的值一样的时候,才会将内存中的值修改为新的修改值。
例如JUC下边的原子自增类,我们以AtomicInteger为例
AtomicInteger atomicInteger=new AtomicInteger(10);
atomicInteger.incrementAndGet();
初始化值为10,然后当有多个线程同时过来对这个变量进行修改的时候,比如有两个线程A跟B,A先读取内存值N,然后最气进行操作自增1,然后新的值是11,然后旧的预期值C与内存值N进行对比看是否相等,但是呢,此时内存值已经被线程B修改为了11,线程A的预期值C与内存值N对比值不一样,所以放弃本次修改,再次获取内存值,进行CAS的这个操作流程。
-
ABA问题:如果某个值一开始是A,后来变成了B,然后又变成了A,你本来期望的是值如果是第一个A才会设置新值,结果第二个A一比较也ok,也设置了新值,跟期望是不符合的。所以atomic包里有AtomicStampedReference类,就是会比较两个值的引用是否一致,如果一致,才会设置新值 假设一开始变量i = 1,你先获取这个i的值是1,然后累加了1,变成了2 但是在此期间,别的线程将i -> 1 -> 2 -> 3 -> 1,这个期间,这个值是被人改过的,只不过最后将这个值改成了跟你最早看到的值一样的值,结果你后来去compareAndSet的时候,会发现这个i还是1,就将它设置成了2,就设置成功了,但是,用AtomicInteger,常见的是计数,所以说一般是不断累加的,所以ABA问题比较少见。如果遇到ABA的问题,一般解决方案是
添加版本号或者添加时间戳 -
无限循环问题:大家看源码就知道Atomic类设置值的时候会进入一个无限循环,只要不成功,就不停循环再次尝试,这个在高并发修改一个值的时候其实挺常见的,比如你用AtomicInteger在内存里搞一个原子变量,然后高并发下,多线程频繁修改,其实可能会导致这个compareAndSet()里要循环N次才设置成功,所以还是要考虑到的,一般是一个线程。
-
多变量原子问题:一般的AtomicInteger,只能保证一个变量的原子性,但是如果多个变量呢?你可以用AtomicReference,这个是封装自定义对象的,多个变量可以放一个自定义对象里,然后他会检查这个对象的引用是不是一个.