共享内存模型
共享内存模型
在线程与锁之间,一个非常简单的并发模型是–共享内存模型。共享内存模型是指通过共享内存来实现并发的模型。
共享内存模型中,当多个线程在并发执行过程中对共享资源进行处理时候,如果没有进行一定的同步处理,那么就会出现读脏、数据无效的问题。在共享内存模型中,非常重要的两个概念是:内存可见性+原子性。
Java中多个线程之间的通信就是通过共享变量来进行的,JVM里面主内存是所有内存共享的,而每个线程拥有自己的工作内存,线程只能在自己的工作内存中处理数据。那么很明显的,在内存之间对共享变量处理过程就容易出现错误问题。
内存可见性
内存可见性保证了Java多线程并发处理过程中,不同的线程对于共享资源的处理,能够保证始终被其他线程观察到。例如对某一个共享数据的处理,其他线程始终能够看到这个共享数据的最新值,无论这个数据是在哪一个线程中进行修改的。
1 | public class Counting { |
在上述代码中,有两个线程,分别对一个共享变量count进行+10000操作,如果程序是正常的,那么输出的值应该是20000,然而因为线程每次进行+1操作之后,修改的值对另一个线程不可见,而对数据进行更新到主内存的时间不定,所以多次测试结果都不相同。同样的,这个程序也违反了变量模型的原子性。
原子性
并发的原子性表现为在多线程操作中,对于某一个共享变量的操作,每个线程的操作都应该是原子性的。例如在Java中,对于一个数据的自增,也就是X++,这个操作实际上是读-改-写模式的操作,当在多线程中操作的时候,可能会存在线程a进行读取。线程b进行读取,同时两个线程分别对其进行++操作,但是最终的结果可能是1,也就是++操作不是原子操作。
维持原子操作的方法,在共享内存模型中,是对共享内存进行加锁操作。通过加锁操作,持有一个共享对象的锁,将一个线程对该对象的操作转换为原子操作,保证程序的原子性。
非常诡异的现象
在Java中,存在着一些非常有意思的操作:指令重排序(乱序执行)。当出现指令重排序的时候,
Java会打乱代码的执行顺序,也就是说,对于代码的执行会处于不可控状态,这个状态跟线程的乱序执行是一样的道理。
1 | 导致在执行过程中发生乱序执行的原因有这么几种情况: |
乱序执行造成的影响:
1 | public class Puzzle { |
上述代码中,正确的执行输出结果应该是:The meaning of life is: 42.但是当乱序执行的时候,可能会出现一种结果:The meaning of life is: 0.解决方案是Java中有些机制可以使程序在编译器、处理器优化时会对有数据依赖的禁止指令重排序,如:volatile、synchronized等。
共享内存模型主要要满足两点需求:内存可见性和原子性,满足这个条件的线程是安全的。