Java线程内存模型
- 工作内存:即java线程的
本地内存
,是单独给某个线程分配的,存储局部变量等,同时也会复制主内存
的共享变量到本地内存
作为副本,目的是为了减少和主内存
的通信频率,提高效率。 - 主内存:存储类成员变量等,所有的线程共享主内存,每个线程都有自己的工作内存
线程的working memory是cpu寄存器和高速缓存的抽象描述:现在的计算机,cpu在计算的时候,并不是从内存读取数据,它的数据读取优先级是:寄存器-高速缓存-内存。线程耗费的是CPU,线程计算的时候,原始的数据来自于内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完成后,这些缓存的数据在适当的时候应该写回内存。当多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及三个特性:原子性、有序性、可见性
每个线程都有自己的执行空间(即工作内存),线程执行的时候用到某变量,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作:读取,修改,赋值等,这些均在工作内存完成,操作完成后再将变量写回主内存。
可见性:当一个共享变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量的副本值,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。
在没有正确同步的情况下,如果多个线程同时访问了同一个变量,该程序就存在隐患,有三种方式可以修复它:
- 不要跨线程共享变量
- 使状态变量为不可变的
- 在任何访问状态变量的时候使用同步
|
|
在上面的程序中,如果running变量没有加volatile,程序会一直运行,陷入死锁。加上volatile便可以正常执行到结束。
总结:
- 对volatile变量的写会立即刷新到主存
- 读volatile变量是会到主存中读值
也可以用下面的方法
总结:虽然running没有volatile关键字修饰,但是读和写running都是同步方法
- 进入同步方法,访问共享变量会去主内存访问
- 退出同步方法,本地内存对共享变量的修改会被更新到主内存。
volatile变量的“原子性”
对一个volatile变量的写操作,只有所有步骤完成,才能被其他线程读取到。
多个线程对volatile变量的写操作本质上是有先后顺序的。也就是说并发写没有问题
注意:
- volatile对类似于i++这种操作不会是原子操作
- volatile并不会有锁的特性
双重检验的单例模式
|
|
总结:
这里volatile修饰符是必须的,如果没有的话,可能发生的情况:当instance为null时,线程A进入同步代码块,进行Singleton的实例化操作,其中包含几个步骤,在实例化到一半的时候线程B调用getInstance方法,instance不为null,返回一个未完全实例化的Singleton。如果加上volatile,Singleton实例化的几个操作就会变成原子的,只有实例化完成了才会被其他线程所看到。
两个instance == null
校验的作用:
- 第一个:我觉得其主要作用就是在instance不为null的时候,直接返回instance,而不需要进入synchronized,提高了多线程环境下的运行效率。
- 第二个:这个就是进行判断进入同步代码块的那个线程的instance是否为null。