为什么要多线程
其实在所有的软件开发人员心里应该有一个开发准则,那就是锱铢必较。就是对于性能和速度的要求是我们不断努力的方向。多线程就是为了实现我们对计算机硬件的最大化利用和并行处理而提出来的解决方案。当然对性能的要求就带来了复杂的算法处理方案。但是其他方面的性能优化也为我们的多线程编程引入了新的麻烦。首先是我们的内存模型。
内存模型
计算机采用虚拟存储的内存管理方式,其实程序块只有正在运行时才会调到了内存用,由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。
当程序处理完数据后会将高速缓存中的数据重新写回主存中。为了解决缓存不一致性问题,通常来说有以下2种解决方法:
1)通过在总线加LOCK#锁的方式
简单说就是计算机的主存的读写都是在总线上传输的,控制器控制着很多电路的闭合,来控制程序的执行。在总线上加锁保证了,在释放锁之前,其他线程无法使用。
2)通过缓存一致性协议
简单说就是当一个线程更改数据内容时,发现这个数据是一个共享变量,就会通知其他线程他们高速缓存中的数据已经被更改,从而保证线程重新从主存中读取数据。
并发编程中的重要属性
原子性:一个操作要么不执行要么执行完毕,在现在的计算机模型中,只有读写是原子的操作。特殊情况下32位系统中读64位的数据也不是原子的。
可见性:就是像前文所说的缓存一致性协议,当一个线程更改了数据确保其他线程能够知道共享变量的数据变化。
有序性:同样是为了追求性能,在执行代码是机器会进行指令重排,但是这种规则能够保证单个线程中的依赖关系不会被破坏,但是多线程中就可能出现问题。
int i = 0;
boolean flag = false;
i = 1; //语句1
flag = true; //语句2
上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。
下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。