一、基础铺垫
首先我们来个例子:
public class AtomicMain { public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newCachedThreadPool(); Count count = new Count(); // 100个线程对共享变量进行加1 for (int i = 0; i < 100; i++) { service.execute(() -> count.increase()); } // 等待上述的线程执行完 service.shutdown(); service.awaitTermination(1, TimeUnit.DAYS); System.out.println("公众号:Java3y---------"); System.out.println(count.getCount()); } }class Count{ // 共享变量 private Integer count = 0; public Integer getCount() { return count; } public void increase() { count++; } }
你们猜猜得出的结果是多少?是100吗?
多运行几次可以发现:结果是不确定的,可能是95,也可能是98,也可能是100
结果不确定
根据结果我们得知:上面的代码是线程不安全的!如果线程安全的代码,多次执行的结果是一致的!
我们可以发现问题所在:count++
并不是原子操作。因为count++
需要经过读取-修改-写入
三个步骤。举个例子:
如果某一个时刻:线程A读到count的值是10,线程B读到count的值也是10
线程A对
count++
,此时count的值为11线程B对
count++
,此时count的值也是11(因为线程B读到的count是10)所以到这里应该知道为啥我们的结果是不确定了吧。
要将上面的代码变成线程安全的(每次得出的结果是100),那也很简单,毕竟我们是学过synchronized锁的人:
在
increase()
加synchronized锁就好了
public synchronized void increase() { count++; }
无论执行多少次,得出的都是100:
结果都是100
从上面的代码我们也可以发现,只做一个++
这么简单的操作,都用到了synchronized锁,未免有点小题大做了。
Synchronized锁是独占的,意味着如果有别的线程在执行,当前线程只能是等待!
于是我们原子变量的类就登场了!
1.2CAS再来看看
在写文章之前,本以为对CAS有一定的了解了(因为之前已经看过相关概念,以为自己理解了)..但真正敲起键盘写的时候,还是发现没完全弄懂...所以再来看看CAS吧。
来源维基百科:
比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。
CAS有3个操作数:
内存值V
旧的预期值A
要修改的新值B
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值(A和内存值V相同时,将内存值V修改为B),而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试(或者什么都不做)。
我们画张图来理解一下:
CAS理解
作者:Java3y
链接:https://www.jianshu.com/p/5c9606ee8e01