类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区(或者叫java内存结构)的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口(下面的图只是一个大概,后面说到java的对象模型会进一步说明)
说到java内存结构(或者叫运行时数据区),不得不说其他的两个概念:Java内存模型/Java对象模型
Java内存模型(java memory model--JMM)
JMM是和多线程相关的,它描述了一组规则或者规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的;java的多线程通信是通过共享内存进行通信的,由于采用共享内存进行通信,在通信过程中会存在一系列问题,例如可见性,原子性,顺序性等。
所以说到JMM,就要展开说说可见性,原子性,顺序性
首先说说我们写代码时,操作变量的规则:
1.Java内存模型规定了所有的变量都存储在主内存
2.线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝
3.线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量
可以来张图看看:
好了接下来我们继续说说可见性,原子性,顺序性
原子性
所谓“原子”操作,是指一组不可分割的操作:操作者对目标对象进行操作时,要么完成所有操作后其他操作者才能操作;要么这个操作者不能进行任何操作
1.基本数据类型(除了long,double64位除外,他们不是原子操作)
int a,b;
a=12;//原子操作
b=++a;//非原子操作,因为“++a”有三步:++,取a,复制给b
2.JDK1.5的版本中提供了java.util.concurrent.atomic原子操作包:AtomicBoolean,AtomicIntegerArray,AtomicReference
3.CAS(Compare And Swap)--sun.misc.Unsafe---Doug Lea
4.更大范围的原子操作:使用lock和unlock原子操作,在jvm以更高层次的指令monitorenter和monitorexit指令开放给我们使用,反应到java代码中就是---synchronized关键字
备注:
1.java.util.concurrent.atomic原子操作包和AQS(AbstractQueuedSynchronizer)都用到了CAS
2.所谓CAS操作: CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
U.compareAndSwapInt(this, WAITSTATUS, expect, update)
有序性
可以总结为:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
3个重要概念:
1.指令重排序
2.先行发生原则(happens-before)
3.as-if-serial语义
1.指令重排序
什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理
① 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
② 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
③ 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
下面看一段代码
int a = 1;
int b = 2;
int c = a+b;
a,b的赋值可以重排序,但是c不可以,因为c依赖a,b
也就是说,可以变成这样:
int b = 2;
int a = 1;
int c = a+b;
上面的例子是编译器和处理器会自动重排序,我们总有一些情况不希望重排序,例如使用volatie,final等关键字,后面会逐一说说这些关键字!
已经说了,总有不希望重排序的情况
可以通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序。
内存屏障,又称内存栅栏,是一个CPU指令
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序
java内存屏障
java 的内存屏障通常所谓的四种即LoadLoad、StoreStore、LoadStore、StoreLoad
1.LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
2.StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
3.LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
4.StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
2.先行发生原则(happens-before)
注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。
3.as-if-serial语义
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不会改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,
因为这种重排序会改变执行结果。
但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序
可见性
当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的,无论是普通变量还是volatile变量都是如此
说说3个关键字
1.synchronized
2.volatile
3.final
1.synchronized
说到synchronized,就是下面两种使用方式:
在dos命令行使用javap -v 类名.class查看如下,结果如下
大家应该知道synchronized关键字,是基于每个对象都是monitor锁,现在了解大概!synchronized关键字使用在方法前和方法内部是不一样的原理,后面在java的对象模型中会详细说明monitor的原理
备注:
在JDK1.6之前synchronized关键字使用起来还是有很多弊端的,所以在JDK1.5的时候,大神Doug Lea提供了线程并发库java.util.concurrent,其中包含了java.util.concurrent.atomic和java.util.concurrent.lock,上面关于原子性提到过!
不过在JDK1.6版本中,Java官方对从JVM层面对synchronized较大优化,也就是你可能听说过的
偏向锁 、轻量级锁 、自旋锁、锁消除 、锁粗化!
所以现在到底使用synchronized或者lock,可以根据自己的使用情况决定
2.volatile
当一个变量定义为 volatile 之后,将具备两种特性
1.保证此变量对所有的线程的可见性,这里的“可见性”,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
2.禁止指令重排序优化。有volatile修饰的变量,相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
volatile语义中的内存屏障
1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;
说说我们常用的单利模式:直接上图
我们通常写单例,并且已经知道肯定会多线程操作,一般会这种写法,但是官方建议,由于synchronized在JDK1.6之后已经有了优化,这里不建议再使用volatitle!(为啥Glide的单例使用了呢)
3.final
我们都知道final关键字用在类,方法,字段都代表什么意思!这里说说不一样的东西!