并发编程的目标与挑战

 If I had only one hour to save the worlds,I would spend fifty-five minutes defining the problem,and only five minutes finding the solution.

如果我只有1小时拯救世界,我将花55分钟定义这个问题而只花分钟去寻找解决方案 ——Albert Einstein

本文讲解的将是多线程的一些重要概念,为接下来自己以及读者更好的理解并发编程做个铺垫。

之后会讲解volatile关键字,CAS , AQS 等等,总之概念是实践的基石

1.1 竞态

多线程编程中经常遇到一个问题就是对于同样的输入,程序的输出有时候是正确的,而有时候却是错误的。这种一个计算结果的正确性与时间有关的现象就被称为竞态(Race Condition)。

如 

02.子线程ThreadDemo启动 获取到flag=false的值 开始睡觉

03.main线程获得了flag=false的值 在循环体中跑了若干次

04.由于03步骤main线程获得了flag=flase,虽然主存变了,但是由于while(true)执行效率太高,根本没有时间让主存中的数据同步到main线程中去,所以main线程一直在死循环

那么,在Java平台中 如何保证可见性呢?

对于上例Demo,我们只需将其flag的声明添加一个volatile关键字即可,即

private volatile boolean flag = false;

这里,volatile关键字所起到的一个作用就是,提示JIT编译器被修饰的变量可能被多个线程共享,以组织JIT编译器做出可能导致运行不正常的优化 (重排序)。另外一个作用就是 读取一个volatile关键字所修饰的变量会使相应的处理器执行刷新处理器缓存的动作

1.3.3 有序性

有序性 指在什么情况下一个处理器上的运行的一个线程所执行的内存访问操作在另外一个处理器上运行的其他线程看来是乱序的。(某书定义)

我的理解:程序运行顺序要与代码逻辑顺序保持基本一致,避免多线程情况由于重排导致的错误

所谓乱序,是指内存访问操作的顺序看起来像是发生了变化。在进一步介绍有序性概念之前,我们需要介绍重排序的概念

重排序:是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段

注:这一块建议了解编译原理 以及汇编

as-if-serial语义:编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变程序执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可以被编译器和处理器重排序。

示例:

double pi = 3.14;  // A  double r = 1.0;     //B double area = pi * r * r; //C

分析:A与C之间存在数据依赖关系,所以C不能排到A的前面,同时B与C之间也存在数据依赖关系,所以,C也不能排到B的前面,但是A与B之间是不存在数据依赖关系的,所以A与B之间是可以进行重排序的。

程序顺序规则:

根据happens-before的程序规则,上面的计算圆的示例代码存在3个happens-before关系:

A happens-before B ; B happens-before C; A happens-before C;

重排序对多线程的影响:

class RecorderExample{     int a = 0;     boolean flag = false;     public void writer(){         a = 1; // 1         flag = true; // 2 }     public void reader(){         if(flag){          // 3             int i = a * a;  // 4              ......     } }  } 

flag是一个变量,用来表示变量a是否已被写入。这里假设有两个线程A和B ,A线程首先执行writer方法,随后线程B执行reader方法。线程B在执行操作4的时候,能否看到线程A在操作共享变量a的写入呢?

答案是:在多线程的情况

50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信