前言
从JDK1.5以后,引入了java.util.concurrent并发包,其中java.util.concurrent.atomic包,方便在无锁的情况下,进行原子操作。在JUC中大部分都是利用volatile关键字+CAS在不用锁的情况来保证线程安全的。本篇文章把这两个知识点给大家一个清晰的解析,只有掌握了关键字volatile和CAS机制,你才能对JUC包有一个彻底的理解。
Java的内存模型JMM
1.1、Java的内存模型(JMM)
要想彻底明白volatile到底是干什么的,你必须知道Java的内存模型(JMM)。网上有很多关于对JMM定义的描述,如果我在按照他们的列出来,那么这一篇文章就变了味道,所以我用自己理解的去阐述Java内存模型,不会用长篇大论去介绍概念,而是依据例子去阐述,我觉得更有意义。
我们知道,共享变量属于所有的线程共享的,为了提高性能,每一个线程都会保存一份共享变量的副本,就是说每一个线程都会从主存中复制一份共享变量到自己的工作内存中去。举例说明:
有一个全局变量count=0,线程1和线程2同时将count+1
上面是一个非常简单的例子,如果对JMM不熟悉的同学很容易脱口而出最终结果为2,但是在多线程下的环境下真的就是我们期望的结果吗?答案是不一定,可能就会出现不同的现象了。
第一个现象:线程1首先获取到CPU的执行权,
1:线程1首先获取CPU的执行权,所以从主存中获取count=0,然后复制一份到自己的工作内存中去。
2:线程1将工作内存中的count+1,此时工作内存count=1,还未来得及刷新到主存中,这时线程2获取了CPU的执行权
3:线程2获取CPU的执行权,所以也从主存中获取count=0,然后复制一份到自己的工作内存中去。
4:线程2将工作内存中的count+1,此时工作内存count=1。
5:是线程1首先刷新到主存中,还是线程2首先刷新到主存中,这个不确定。
上面线程1和线程2两个线程的工作内存的count都是1,但是它们什么时候刷新到主存中,无法确定,可能是线程1首先将count=1刷新到主存中,也可能是线程2首先将count=1刷新到主存中,不管哪一个线程首先将它的工作内存中count刷新到主存中,那此时主存也会count=1,这个结果与我们想象的不一样。
第二个现象:线程2首先获取到CPU的执行权,
1:线程2首先获取CPU的执行权,所以从主存中获取count=0,然后保存到自己的工作内存中
2:线程2的count副本+1,此时count=1,但是还未来得及刷新到主存中,线程1获取了CPU的执行权。
3:线程1获取CPU执行权后,会从主存中拷贝一份count=0,到自己的工作内存中去。
4:线程1的count副本+1,此时count=1.
5:是线程2首先刷新到主存中,还是线程1首先刷新到主存中,这个不确定
上面两种现象不管是线程1首先获取CPU执行权,还是线程2获取CPU执行权,最终的结果是一样的,那就是count=1。这个结果并不是我们要的结果,导致出现这个结果的原因就是并不知道工作内存中的值什么时间才会刷新到主存中去
第三种现象:线程1首先获取到CPU执行权,然后count+1,并刷新到主存中后线程2才获取CPU的执行权。
1:线程1首先获取CPU的执行权,从主存中复制一份count=0到自己的工作内存中去。
2:线程1将工作内存的count+1,此时count=1
3:在线程2获取CPU执行权之前,线程1就将自己工作内存count=1刷新到主存中去。
4:此时主存中的count=1
5:线程2获取CPU的执行权,从主存中复制一份count=1到自己的工作内存中去。
6:线程2