JVM 内存模型
看图 :
8个内存模型的基本操作指令
在主内存和工作内存之间通过8个操作来完成交互 :
lock (锁定): 作用主内存中的变量,标记其为线程独占,即锁定;
unlock (解锁): 作用主内存中的变量,把一个锁定的变量释放,该变量可被其他线程锁定;
read (读取): 作用主内存中的变量,把变量从主内存传输到线程的工作内存中;
load (载入): 载入,把read出的主内存的值,载入到工作内存中;一定与read一同出现;
use (使用): 作用与工作内存的变量,把变量传递给执行引擎,执行计算/赋值等操作;
assign (赋值): 作用于工作内存的变量,把执行引擎完成操作后的结果,赋值给工作内存的变量;
store (存储): 作用于工作内存的变量,把变量值传送到主内存;
write (写入): 写入,把store出工作内存的值,回写入主内存中;一定与store一同出现;
store+write 刚好就是 read+load 的反向操作;
内存模型操作的特性:原子性、可见性、有序性
原子性:基本上我们认为java内存模型中已经保证对一个变量操作的原子性,操作包括:read、load、use、assign、store、write(除了long和double,但是这种特殊问题也不用太过担心,可以忽略)
可见性:volatile对象的每一次操作都会立即刷新主内存,对其他线程可见;
有序性:通过提供volatile防止指令重排序,和synchronized来指定同意时间只允许一个线程对同一个对象执行lock操作,来保证程序执行的有序性;
先行发生原则
两个操作互不影响时,时允许指令重排序的,但是,当a操作的结果时被b操作使用的,那么a操作一定先行发生于b,a和b时不允许对调顺序的;
volatile
声明了volatile的对象,保证了此变量对所有线程可见(可见性),并且时立即可见,也就是每一个线程对该对象执行use时都要从主内存中获取最新的对象值,当某个线程修改(assign)了该变量时,一定会同时通知到所有读取并载入了该对象的线程,你们获取的值已经失效;
但是并不代表volatile就是线程安全的,当多线程同时修改该对象时,还是有可能出现把非正确的值传回主内存的情况的;这个时候还是需要使用到synchronized或者concurrent保中的原子类,来保证对volatile对象操作的原子性。
使用volatile的第二个意义,是防止了指令重排序;(JDK1.5以后才有的特性)
比如:
int a = 1;
int b = 0;
int c = 5;
再虚拟机执行时,很可能时先创建了c后创建的a,因为这三条语句并不影响执行结果;
这时加上volatile:
int a = 1;
int b = 0;
volatile int c = 5;
那么,c一定再a之后才会创建;
synchronized
定义了synchronized的类、实例对象、代码块、方法都会在编译后,形成两个引用类型 : monitorenter 和 monitorexit;用这两个对象明确的指向锁定和解锁的对象;
当一个线程尝试获取一个synchronized对象的锁时,monitorenter当前没有被其他线程锁定,monitorenter会对计0数+1;并把monitorenter指向线程本身的monitor;
当退出时,monitorexit会把计数-1,锁就被释放了;
由于获取锁时monitorenter时指向线程本身的monitor的,所有对于已经获取了锁的线程,时不需要再次尝试获取锁的;
重入锁(ReentrantLock)
ReentrantLock 是 java.util.concurrent 包下的一个重入锁实现类;
与synchronized不同,synchronized 是java原声语法层面的互斥锁;而 ReentrantLock 是api层面的互斥锁;
通过具体的代码,可以实现:
- 等待可中断;
对于长时间获取不到锁的线程,可以中断等待,去执行其他指令; - 公平;
可以通过申请锁的时间顺序来依次公平的获取锁,通过一个链表数据结构定义了一个Qeueu来实现;synchronized是非公平的; - 锁可以绑定多个条件;
一个lock对象可以同时绑定多个condition来实现不同条件下的等待和唤醒操作,调用多个new Condition()即可;而synchronized只能隐含的实现一个条件的等待和唤醒;
锁的种类
- 从实现层面,分原声语法锁和api锁
- 从锁方式,分为互斥锁和读写锁
互斥锁就是锁住的对象,任何操作都是要绝对互斥的;
读写锁就是,读读共享锁,读写互斥。写写互斥;通过定义读锁和写锁,读锁计数和写锁计数来实现; - 从锁效率,分为悲观所和乐观锁
悲观所就是,任何时候都认为有竞争关系,获取锁对象就锁住对象;
乐观锁就是,认为一般不会发生竞争关系,通过对象头设置标记位来设置锁,并设置版本号,当发生竞争时通过标记位来标记现在是存在竞争还是没有,用版本号来指定线程执行的操作是否有效,是否需要等待锁并重新执行; - 从锁量级,分偏向锁,轻量级锁、重量级锁
偏向锁是几乎不存在竞争的情况下,一个线程获取过该对象的锁,那么这个线程下次再尝试获取锁时,如果锁依然保持偏向状态,并且偏向的就是该线程,可以直接获取锁;偏向状态和偏向的线程都再对象头中记录;如果时非偏向状态,则升级为轻量级锁;
轻量级锁,也是再对象头中设置一个标记位,代表锁是轻量级的,并再对象头中设置owner指向,尝试设置owner及锁定状态即可,不需要真的锁住对象;当发生竞争,即尝试获取锁时发现锁已经存在owner并是锁定状态,则升级锁为重量级锁;