参考来源:
① http://ifeve.com/高并发编程必备基础/
② https://www.cnblogs.com/java-zhao/p/5124725.html
一. 什么是线程安全问题?
① 线程安全:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
② 线程问题:
线程安全问题是指当多个线程同时读写一个状态变量,并且没有任何同步措施时候,导致脏数据或者其他不可预见的结果的问题。Java中首要的同步策略是使用Synchronized关键字,它提供了可重入的独占锁。
③ 扩展:
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
二. Java内存模型
① Java内存模型(JMM)
Java内存模型的主要目标:定义在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
Java内存模型规定了所有的变量都存放在主内存中,当线程使用变量时候都是把主内存里面的变量拷贝到了自己的工作空间或者叫做工作内存。
② 内存模型
JMM模型所有共享变量存于主内存;每一条线程都有自己的工作内存(就是上图所说的本地内存);工作内存中保存了被该线程使用到的变量的主内存副本。
线程对变量的操作都要在工作内存中进行,不能直接操作主内存;不同的线程之间无法直接访问对方的工作内存中的变量;不同线程之间的变量的传递必须通过主内存。
上边的变量指的是共享变量(实例字段、静态字段、数组对象元素),不包括线程私有变量(局部变量、方法参数),因为私有变量不会存在竞争关系。
③ 注意
主内存与工作内存只是一个概念,与堆栈内存没有关系,下边的类比只是帮助理解
主内存:对应于Java堆中的对象实例数据部分(注意:堆中还保存了对象的其他信息,eg.Mark Word、KlassPoint和用于字节对其补白的填充数据)
工作内存:对应于栈中的部分区域
三. 8条内存屏障指令
下面只列出6条与之后内容相关的,其余的查看《深入理解Java虚拟机》
lock:作用于主内存,把一个变量标识为一条线程独占的状态
unlock:作用于主内存,把一个处于锁定的变量解锁
下边四条是与volatile实现内存可见性直接相关的四条(store、write、read、load)
store:把工作内存中的变量的值传送到主内存中
write:把store操作从工作内存中得到的变量值放入到主内存的变量中
read:把一个变量的值从主内存中传输到线程的工作内存
load:把read操作从主内存中获取到的变量值放入工作内存的变量中去
注意:
一个变量在同一时刻只允许一条线程对其进行lock操作
lock操作会将该变量在所有线程工作内存中的变量副本清空,否则就起不到锁的作用了
lock操作可被同一条线程多次进行,lock几次,就要unlock几次(可重入锁)
unlock之前必须先执行store-write
store-write必须成对出现(工作内存-->主内存)
read-load必须成对出现(主内存-->工作内存)
四. 当线程操作一个共享变量时候操作流程
① 线程首先从主内存拷贝共享变量到自己的工作空间
② 然后对工作空间里的变量进行处理
③ 处理完后更新变量值到主内存
那么假如线程A和B同时去处理一个共享变量,会出现什么情况那?
首先他们都会去走上面的三个流程,假如线程A拷贝共享变量到了工作内存,并且已经对数据进行了更新但是还没有更新会主内存(结果可能目前存放在当前cpu的寄存器或者高速缓存),这时候线程B拷贝共享变量到了自己的工作内存进行处理,处理后,线程A才把自己的处理结果更更新到主内存或者缓存,可知 线程B处理的并不是线程A处理后的结果,也就是说线程A处理后的变量值对线程B不可见,这就是共享变量的不可见性问题。
构成共享变量内存不可见原因是因为三步流程不是原子性操作,下面知道使用恰当同步就可以解决这个问题。
五. 变量对所有线程的可见性
可见性:线程1对共享变量的修改能及时被线程2看到
共享变量不可见的原因
① 共享变量更新后的值没有在工作内存和主内存之间及时更新
② 线程交错执行
③ 指令重排序结合线程交错执行
实现共享变量及时更新的措施
线程1修改过共享变量后,将共享变量刷到主内存,然后,线程2从主内存读取该共享变量,将该共享变量载入到工作内存中
注意:在短时间内的高并发情况下,如果发生下列三种情况,则线程2就读不到线程1修改过的最新的值了,
① 可能线程1根本来不及将修改过后的共享变量刷到主内存(这个时间非常短,但是还是有)的时候,线程2就已经读取了原有的主内存变量到其工作内存中。
② 可能线程1虽然将修改过后的值刷到了主内存中,但是线程2的工作内存中的变量副本还没来得及从CPU刷新回来,所以线程2读取到的还是原来的工作内存中的变量副本
③ 可能线程1根本来不及将修改过后的共享变量刷到主内存的时候,同时,线程2的工作内存中的变量副本还没来得及从CPU刷新回来
注意:
工作内存中的变量副本在使用之后,不会立刻消失掉,会一直存在,这样其值也一直不变,直到对其进行写操作或数据从CPU中刷新回来(类比volatile-read的作用)。
网友评论