文章目录
前言
想要学好并发编程,那么久要完全掌握并发编程的相关概念,接下来我们将一起探讨并发编程中的三个重要概念
一、并发编程的三个重要概念是什么?
1、原子性
2、可见性
3、有序性
二、概念引入
1.cpu的缓存解析
为了更好的理解并发编程的三个重要概念,这里我们有必要先探讨下之前讲过的cpu的缓存过程,以加深对并发编程概念得理解。
这是cpu的内存模型,接下来我们分析下两行代码,如下截图
我们可以看到在cpu内存模型下这两行代码的演变过程是这样的,先是cpu1的主内存也就是RAM中的i赋值为1,然后将i的一份副本保存到cpu1的缓存中,缓存中的 i + 1赋值给i,于是缓存中i的值就变成了2,最后将2赋值给cpu1主内存中的i。最终完成这两行代码的执行。这个过程可能在cpu2这边执行的过程也是一样的,这就导致了一个问题:cpu1执行完代码后,已经将i的值写为2,但是由于cpu1没改变i的值之前cpu2已经将主内存中i = 1的值读入自己的缓存中,导致cpu2缓存中i的值也为1,那么cpu运行这两行代码的结果也为2。于是就有了线程不安全的问题了。
2.cpu如何解决缓存不一致问题
那么cpu如何解决这种主内存和缓存中的数据不一致的问题的呢?有两种方法。
第一、对数据总线进行加锁
这种方法的解决过程是这样的,当cpu1执行程序的时候锁住数据总线,这时候也就只有cpu1能够访问数据总线的数据。当cpu1执行完程序后再对数据总线进行解锁,这时cpu2开始执行程序了,也给数据总线进行上锁。这样就保证了数据一致性问题。但是这样会使得cpu串行化,降低了运行效率,多核cpu就没有意义了,于是就有了第二种方法。
第二、cpu告诉缓存一致性协议
这种方法是如何解决问题的呢?首先当cpu写入数据的时候,如果发现该变量被共享,也就是该变量在其它cpu中有副本,那么cpu就会发出一个信号,通知别的cpu:该变量的缓存无效。然后,当其它cpu访问该变量的时候会到主内存中去拿数据。相比第一种方法,效率就提高了很多。
好,现在大家对cpu的缓存有更深的理解了,接下来我们开始深入了解并发编程的三个概念。
二、并发编程的三个概念
1.原子性
原子性也就是一个或者多个操作,要么执行都成功,要么执行都失败。只要有一个操作失败,那么其它的所有的操作都要置为失败。这跟数据库ACID概念得的原子性Atomic是一样的。
讲个大众的例子,小明给小花转账,当操作成功的时候:小明的账户余额会减少,而小花的账户余额会增加。当某个操作失败的时候:小明的钱不变,小花的钱也不变。不可能出现小明的钱减少了而小花的钱不变,或者小明的钱不变,而小花的钱增多了这两种情况。这就是原子性。
1.可见性
可见性又是怎么样的呢?我们直接看例子
如下截图:
如图:我们可以看到线程1中i的值为10,而线程2中j的值可能为0,那么这时候就不能体现可见性了。当线程1的i为10线程2中的j为10。这是才体现了可见性
总结下来就是。一个线程改变了共享变量的值,其它的线程要看到这个变量改变后的值,这就是可见性。
3.有序性
有序性就更好理解了,字面意思,就是执行代码的过程中由于java优化的缘故,可能会对代码的执行顺序进行重排。
如图,Thread1中如果代码进行了重排序,那么这两句代码就有可能颠倒过来,那么flag = true就会先执行。如果恰恰在线程1执行完flag = true值后Thread2开始执行,那么程序就会进入循环,执行里面的object.name。但是Thread1都还没有对object进行初始化,于是这里就报错了。这里是不符合有序性的。所以有序性对并发编程来说很重要。
四、如何保证原子性、可见性、有序性。
1.原子性
对基本数据类型变量的读取和赋值是保证了原子性的,要么都成功,要么都失败,这些操作是不可能被中断的
a = 10 满足原子性
b = a 不满足1、read a; 2、assign b;
a++ 不满足1、read a; 2、add; 3、assign a;
a = a + 1 不满足1、read a; 2、add; 3、assign a;
2.可见性
voilatile关键字能够保证可见性
3.有序性
happens-before relationship
保证happens-before原则才能保证有序性
1、代码执行的顺序,编写在前面的执行要先于编写在后面的
2、unlock发生在lock之后
3、volatile关键字修饰的变量,对一个变量的写操作要先于读操作
4、传递规则,操作a先于操作b,操作b先于操作c,那么操作a先于操作c。
5、线程启动规则、线程start先于run
6、线程中断规则、线程的interrupt这个动作必须先于捕获动作
7、对象销毁规则、初始化必须发生在finalize之前
8、线程终结规则、线程的所有操作发生在线程死亡之前。