volatile
- 语义:
volatile具有和synchronized的一样的语义,被volatile修饰的变量在 ‘读’ 时JMM会将该线程所对应的’本地内存’置为失效,线程接下来会从 ‘主内存’ 中直接获取,在 ‘写’ volatile 修饰的变量时JMM会将该线程对应的本地内存的共享变量直接写入到 ‘主内存’ 中。
- 使用
被volatile修饰的变量将会 ‘禁用缓存’ , 并且禁止 ‘指令重排’(指令重排分为虚拟机重排和处理器重排)
volatile 声明这个字段易变(可能被多个线程使用),Java内存模型负责各个线程的工作区与主存区的该字段的值保持同步,即一致性。
static 声明这个字段是静态的(可能被多个实例共享),在主存区上该类的所有实例的该字段为同一个变量,即唯一性。 - code
private volatile String a;
synchroized
-
语义:
在进入synchronized修饰的方法或代码块时会进行lock操作,并且JMM会将线程本地内存清空,临界区内使用共享变量将从’主内存’中同步,在出synchronized修饰的方法或代码块时会进行unlock操作,并且JMM会将线程的本地内存的共享变量刷新到主内存中。
-
使用:
synchronized是java内置锁,可以放在 ‘普通方法’ ‘静态方法’ 和 ‘代码块’ 上,如果是放在普通方法上则代表着 ‘锁的是当前对象’,放在静态方法上 ‘锁的是类(.class)’ ,如果是synchronized代码块写法则是synchronized(‘需要锁的对象或者是类’){‘临界区’}。
注:synchronized锁的是一个对象而不是某个方法
-
code
//锁的是当前对象
public synchronized void test(){}
//锁的是类(.class)
public static synchronized void test1(){}
//这里锁的是this 也就是当前对象
public void test3(){
synchronized (this){
// 这里代表临界区
}
}
可见性·原子性和有序性
- 语义
可见性 就是指线程A在修改共享变量后线程B你能立马看的到,称之为可见性。
原子性 就是指一个操作或一组操作不可中断,要么全部执行,要么全都不执行,称之为原子性。
有序性 就是指程序按照我们的代码顺序进行执行,称之为有序性(程序员以为是按照我们的代码顺序执行的,其实经过了虚拟机和处理器指令重排后的顺序执行的)。
java内存模型
-
运行时数据区域
java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图:
-
虚拟机栈
虚拟机栈是线程私有的,也可以称之为线程栈,虚拟机栈所描述的就是java执行方法的一个简易版内存模型,每个方法在执行时都会创建 ‘栈帧’ 用于存存储:
局部变量表:存放方法参数和方法内部定义的局部变量
操作数栈: 其实就是在执行每一行命令,操作记录
动态链接:指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量
方法出口:方法返回地址当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址’ 等信息’
局部变量表:存储各种基本数据类型,包括 byte,short,int,long,float,dubbo,char,boolean 和 对象引用(引用类型不是对象本身,是一个指向对象的一个地址)
-
本地方法栈
本地方法栈和虚拟机栈是一样的,区别就是虚拟机栈是为java方法(也就是字节码)服务的,本地方法栈是为虚拟机使用到的Native服务的。(可以理解为是操作系统的方法)
-
程序计数器
程序计数器也是线程私有的,大白话意思就是存储线程的执行地址,因为java多线程是通过Cpu时间片轮询方式执行的,比如线程A执行到一半,Cpu时间片使用完了,这时线程A就会在自己的程序计数器中存储执行地址保证下次从这里开始继续执行,然后线程B开始执行。
-
Java堆
java堆就是存储所有对象的实例,数组(数组引用是存放在Java栈中的)和常量池,也是最大的一块内存,-xms 初始堆大小 和-xmx最大堆大小
-
方法区(在1.8之前也叫用永久代,之后称之为元空间,使用的是本地内存)
方法区也是线程共享的区域,存储已经被虚拟机加载的类信息(包括类的名称、方法信息、字段信息),常量和静态变量
-
常量池 存在于方法区中 (jdk1.7 后常量池移到堆中)
常量池也可以说是String常量池,因为String是final修饰的,是常量且不可变。
code:
// 以下代码JMM就会在常量池中查找"lantao"是否存在,如果存在则将地址赋值给a,如果不存在则先创建一个存储在常量池中然后将地址赋值给a;
String a = "lantao";
// 以下代码会创建你String对象,区别就是
//1-创建String对象;
//2-在常量池中查;找"lantao",如果存在则赋值给1,如果不存在则在常量池中创建'lantao'再将地址赋值给1;
//3:String对象将地址赋值个变量b;
String b = new String("lantao");
// 所以 这里是 false ,因为地址不一样,
// b的地址指向的是 heap 中的String,String对象在只指向常量池中的'lantao'
// a的地址是直接指向 常量池中的'lanntao'
System.out.println(a == b); //false
photo:
-
java内存模型的8个操作
1–lock(加锁):用在主内存变量上,标记这个变量是一个线程独占状态,其他线程不可锁定;
2–unlock(解锁):用在主内存变量上,对已经加锁的变量进行解锁,释放锁后其他线程可进行对该变量的加锁;
3–read(读取):用在主内存上,将主内存中的变量读取到线程本地内存中,以便后续的load操作;
4–load(载入):用在线程的本地内存上,将read读取到本地内存的变量载入到本地内存的变量副本中(这里个人理解是载入到虚拟机栈(线程栈)的栈帧中的局部变量表中)
5–use(使用):用在线程本地内存上,把本地内存的变量给执行引擎,每当需要使用这个变量的值字节码指令会执行这个操作(大白话就是使用变量);
6–assign(赋值):用在线程本地内存上,把执行引擎获取到的值赋值本地内存的变量,每当需要使用这个变量的值字节码指令会执行这个操作(大白话就是给变量赋值)
7–store(存储):用在本地内存上,将本地你内存中的变量的值存储到主内存中;
8–write(写入):用在主内存上,把store操作从本年内存获取的值放入到主内存的变量中;
-
java内存模型操作的规则
1–不允许read和load,store和write单独使用,需要组合使用;
2–不允许线程丢弃它assign过的变量,意思就是只要是变量在本地内存中变更了,就一定要同步到主线程中;
3–不允许线程在没有assign过变量,就将变量同步到主内存中,意思就是不允许线程在没有改变过变量的前提下,将变量store write到主内存中;
4–一个新的变量只能在主内存中诞生,意思就是在使用变量必须是从主内存read load下来的;(原文意思:对一个变量实施use,store之前,必须要执行过assign和load)
5–一个变量同一时间只可以让一个线程lock,并切可以让这个线程lock多次,但也需要unlock多次才可以解锁;
6–一个变量被线程lock时,线程的本地内存将会被清空,在使用变量的时候需要进行load或assign操作来初始化;
7–变量在为为lock的时候,不可以unlock,当然,也不可以unlock别的线程lock的变量;
8–对一个变量unlock之前,需要将assign过的变量都store,write到主内存中;