Java并发编程的3个特性

1、原子性

原子行:即一个或者多个操做做为一个总体,要么所有执行,要么都不执行,而且操做在执行过程当中不会被线程调度机制打断;并且这种操做一旦开始,就一直运行到结束,中间不会有任何上下文切换(context switch)。java

咱们用银行帐户转帐问题来形象的解释一下原子性(固然银行帐户转帐涉及到的问题比较多,咱们这里只是来比拟一下)多线程

举例一:
好比张三向李四转帐200元,能够分解成以下步骤:
1)从张三帐户减去200元
2)给李四帐户加上200元
若是只执行步骤1),没有执行步骤2),问题就来了,张三说他给李四转钱了,李四说他没收到,银行该怎么处理这个事情呢?将该操做加上原子性就能够很好的解决转帐问题。并发

举例二:
在java开发中咱们常用以下语句优化

int i = 0; //语句1 i++; //语句2

 

语句1是一个原子性操做。spa

语句2的分解步骤是:
1)获取 i 的值;
2)计算 i + 1 的值;
3)将 i + 1 的值赋给 i;
执行以上3个步骤的时候是能够进行线程切换的,所以语句2不是一个原子性操做线程

2、可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看到修改的值。code

举例:内存

private int i = 0; private int j = 0; //线程1 i = 10; //线程2 j = i;

 

线程1修改i的值为10时的执行步骤:
1)将10赋给线程1工做内存中的 i 变量;
2)将线程1工做内存中的 i 变量的值赋给主内存中的 i 变量;开发

当线程2执行j = i时,线程2的执行步骤:
1)将主内存中的 i 变量的值读取到线程2的工做内存中;
2)将主内存中的 j 变量的值读取到线程2的工做内存中;
3)将线程2工做内存中的 i 变量的值赋给线程2工做内存中的 j 变量;
4)将线程2工做内存中的 j 变量的值赋给主内存中的 j 变量;编译器

若是线程1执行完步骤1,线程2开始执行,此时主内存中 i 变量的值仍然为 0,那么线程2获取到的 i 变量的值为 0,而不是 10。

这就是可见性问题,线程1对 i 变量作了修改以后,线程2没有当即看到线程1修改的值。

3、有序性

有序性:即程序执行的顺序按照代码的前后顺序执行。

举例一:

int i = 0; int j = 0; i = 10; //语句1 j = 1; //语句2

 

语句可能的执行顺序以下:
1)语句1 语句2
2)语句2 语句1

语句1必定在语句2前面执行吗?答案是否认的,这里可能会发生执行重排(Instruction Reorder)。通常来讲,处理器为了提升程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行前后顺序同代码中的顺序一致,可是它会保证程序在单线程环境下最终执行结果和代码顺序执行的结果是一致的。
好比上面的代码中,语句1和语句2谁先执行对最终的程序结果并无影响,那么就有可能在执行过程当中,语句2先执行而语句1后执行。

举例二:

int i = 0; //语句1 int j = 0; //语句2 i = i + 10; //语句3 j = i * i; //语句4

 

语句可能的执行顺序以下:
1)语句1 语句2 语句3 语句4
2)语句2 语句1 语句3 语句4
3)语句1 语句3 语句2 语句4

语句3是不可能在语句4以后执行的,由于编译器在进行指令重排时会考虑数据的依赖性问题,语句4依赖于语句3,所以语句3必定在语句4以前执行。

接下来咱们说一下多线程环境。

举例三:

private boolean flag = false; private Context context = null; //线程1 context = loadContext(); //语句1 flag = true; //语句2 //线程2 while(!flag){ Thread.sleep(1000L); } dowork(context);

语句可能的执行顺序以下:
1)语句1 语句2
2)语句2 语句1

因为在线程1中语句一、语句2是没有依赖性的,因此可能会出现指令重排。若是发生了指令重排,线程1先执行语句2,这时候线程2开始执行,此时flag值为true,所以线程2继续执行dowrk(context),此时context并无初始化,所以就会致使程序错误。

所以能够得出结论,指令重排不会影响单线程的执行结果,可是会影响多线程并发执行的结果正确性。

总结:一个正确执行的并发程序,必须具有原子性、可见性、有序性。不然就有可能致使程序运行结果不正确,甚至引发死循环。

相关文章
相关标签/搜索