内存模型: 可以理解为,在特定的操作协议下,对特点的内存
或高速缓存
进行读写访问
的过程的抽象
。
在运算时,将需要使用到的数据从内存复制到缓存(Cache)中
,以此让计算能更快的进行
,计算结束后再从缓存同步回内存中
,这样就无需频繁的等待缓慢的内存读写
。
缓存一致性: 在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存。
主内存是虚拟机内存的一部分。
Java 内存模型规定所的变量都存储在主内存中。
变量数据: 最根源的数据,线程不能直接操作。
工作内存中保存了被该线程使用到的变量
的主内存副本拷贝
,线程对变量的所有操作(读取,赋值等)
,都必须在工作内存中进行
,不能直接读写主内存中的数据。不同线程间也无法直接访问对方工作线程中的变量
。
变量数据: 从主内存中拷贝来的副本,其他线程无法访问,操作结束后需写回主内存。
虚拟机实现时,必须保证下面提及的每一种操作都是原子的
、不可再分的
。
主内存
的变量,把变量标记为某个线程独占。主内存
的变量,释放一个锁定的变量。主内存
的变量,把一个变量的值从主内存传输到工作内存
中,以便后续 load
动作使用。工作内存
的变量,将 read
过来的值放入工作内存
的变量副本
中。工作内存
的变量,把 工作内存
中存在的一个变量传递给执行引擎,每当虚拟机遇到一个需要使用变量的值时就会执行这个操作。工作内存
的变量,把从执行引擎接收到的值,赋值给工作内存
中的一个变量,当虚拟机遇到赋值操作时候执行操作。工作内存
的变量,把工作内存
中的一个变量传递给主内存,一遍后续 write
动作使用。主内存
的变量,把 store
操作的值放入主内存的变量中。将变量从主内存复制到工作内存: 顺序执行 read
和 load
,只需按顺序即可,中间可插入其他指令。
从工作内存写回主内存: 顺序执行 sore
和 write
,只需按顺序即可,中间可插入其他指令。
特性: 保证此变量对所有线程的可见性
,当一条线程修改了这个变量值,其他线程会立即收到通知。
volatile 关键字保证线程可见,不代表线程安全:
以下为 race ++
的反编译结果
volatile 关键字
保证了线程可见性,因此只要去读数据,就可以拿到正确的数据
而这里的读操作,只有 getstatic
字节码指令,将数据读入栈顶(每个线程各自拥有,对外部可见)
而后续的不管是递增 iadd
还是回写回主内存 putstatic
指令之前都没有再去读数据
因此如果这期间如果数据发生了变更
,在栈顶的数据就成了旧数据
,回写回主内存就会产生覆盖
,线程不安全
。
普通变量仅仅会保证在该方法的执行过程中,所有依赖赋值结果的地方都能获取到正确的结果
,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致
。
这么做的目的是一种机器级的优化操作,使得某个部分的汇编代码被提前执行
。
volatile
可以避免这种的优化操作。
内存屏障:
volatile
是通过 内存屏障
来防止指令重排序的,反编译后即下图红色框框中的部分。
Java 内存模型是围绕 并发过程中
如何处理 原子性
、可见性
和 有序性
3 个特征来建立的。
直接 保证原子性的操作
包括:read
、load
、assign
、use
、store
、write
基本数据类型的访问读写都是具备原子性的
例外的只有 long
和 double
,具有非原子协定,但是各大虚拟机实现时,都把他们的读写操作作为原子操作来对待,因此知道下就好了,不需要太在意。
为了保证原子性,虚拟机提供字节码 monitorenter
和 monitorexit
来操作 lock
和 unlock
,反应到代码层面就是 synchronized
关键字,因此 synchronized
块的操作也具有原子性。
可见性: 可见性是指,当一个线程修改了共享变量的值,其他变量能立即得知这个修改。
保证可见性的关键字: volatile
、synchronized
、final
。
有序性: 如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无需的。前半句是指 “线程内表现为串行的语义”,后半句是指 “指令重排序” 现象和 “工作内存和主内存同步延迟” 现象。
防止指令重排序关键字: volatile
、synchronized
。
synchronized
同时满足 3 大特性,看起来十分 “万能”,但过度滥用回导致性能问题。
即多线程同时操作一个变量导致值不正确的线程不安全问题。
即不需要同步器,在代码中能直接使用
volatile
变量的写操作优先于后面这个变量的读操作,“后面” 是指时间上的先后顺序。Thread.join() 方法结束
、Thread.isAlive() 返回值
等手段检查到线程已经终止执行。Thread.interrupt()
方法检查到是否有中断发生。尚未启动
的线程状态Runable
和 Ready
状态,处于此状态下的线程可能处于 正在执行
或者 等待 CPU 分配执行事件
。不会被分配执行事件
,需要 等待被其他线程唤醒
。以下方法会让线程进入 Waiting
。Timeout
参数的 Object.wait()
方法。Timeout
参数的 Object.join()
方法。LockSupport.park()
方法。不会被分配 CPU 执行时间,不过无需其他线程显式的唤醒,在一定时间后由系统自动唤醒
。以下方法会让线程进入 Time Waiting
。Thread.sleep()
。Timeout
参数的 Object.wait()
方法。Timeout
参数的 Object.join()
方法。LockSupport.parkNanos()
方法。LockSupport.parkUnit()
方法。“阻塞状态”
在等待获取一个 排他锁
,需要 另一个线程放弃这个锁的时候发生
。已终止
线程的状态,现在已经 结束执行
。