目录
1.JMM
内存模型
1.java[内存模型](Java Memory Model) 和 [内存结构]
2.JMM规定了在多线程下对共享数据的读写时,对数据的原子性 有序性 可见性的规则和保障..
2.原子性
1.原子性问题
i++和i--不是原子性操作! 所以一个i++指令会在执行过程中被另一个线程执行!
2.问题分析
(1)共享的变量信息是放在主内存中的,线程呢是在工作内存中
多线程情况下指令交错产生的问题:
3.问题解决
java中通过synchronized来保证了原子性
EntryList: 排队等候区:阻塞住...
Owner: 有一个一个线程可以进入成为owner
monitorExit: 表示owner中线程执行完毕,会通知EntryList中等待的线程。 然后就可以拥有抢夺owner的机会,当等待的线程成为owner后就会执行monitorenter锁住monitor区域..
WaitSet: 是线程wait() notify()的时候需要用到的区域...
3.可见性
一.可见性问题
二.可见性问题分析
线程t1对于t2修改后的值不清楚的原因是t1线程频繁的读取boolean这个变量。然后即时编译器就会视为热点代码。将boolean的值缓存到高速缓存中...所以t1每次读取都是从自己的工作内存中读取..
主内存中改了值,其实t1线程是感知不到的...、
volatile: 一个线程写,多个线程读的场景....无法保证原子性...
这也是为什么打印日志不用system.out.print(),性能低
4.有序性
1.单线程情况下jvm对其指令重拍是不太影响结果的..(指令重拍是一种优化)
2.多线程下指令重排就会产生一些问题!单例模式下双重锁机制的变量需要加volatile,否则会指令重排造成问题...使得返回的对象不完整
volatile保证了禁止指令重排序...
4.CAS与原子类
CAS是配合volatile配合使用的一项技术.体现的是一种乐观锁的思想,是一种无锁并发技术。
1.CAS适用于哪些场景下???
- 竞争不激烈,多核CPU的情况下.因为等待的线程并不是进行进入阻塞状态,而是
一种在尝试尝试..其他线程也需要占用CPU资源..如果竞争激烈,会影响效率!
2.获取"主内存中值时",为了保证变量的可见性,需要使用volatile来修饰!集合CAS和volatile可以实现无锁并发!
3.和synchronized的比较:
该种并发技术并不会使等待的线程进入阻塞状态,而是不断尝试的方式来尝试操作.
但是当竞争激烈的时候,等待的线程会占用过多CPU资源,导致效率下降!
4.CAS操作底层依赖于一个Unsafe类直接调用操作系统底层的CAS指令!
二.底层实现
CAS操作底层依赖于一个Unsafe类直接调用操作系统底层的CAS指令三.原子操作类
java中的悲观锁: synchronized
java中的乐观锁: CAS
5.synchronized优化
Java HotSpot虚拟机中,每个对象都有对象头(包括class指针和Mark Word)。mark word平时存储这个对象的hash码,分代年龄,当加锁时,这些信息就根据情况被替换为标记位,线程锁记录指针,重量级锁指针,线程ID等内容。
5.1轻量级锁
每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
过程: 这时线程会在虚拟机栈中开辟一块被称为Lock Record的空间。线程通过cas获取锁,一旦获取到锁,会复制该对象的mark Word,并且将Lock Record中的owner指针指向该对象,该对象的对象头的Mark Word的前30个bit也会指向栈中锁记录的指针,这就实现了对象与线程的绑定。
这时候也有其他线程想要获取该对象该怎么办??
- 自旋等待:
线程自己在不断的轮询循环,尝试着看一下目标对象的锁有没有被释放..这种方式区别于被操作系统挂起,因为对象的锁很快被释放的话,自旋就不需要系统中断
和现场恢复..所以效率高一些。
自旋相当于cpu在空转,如果时间过长会占用资源..如果自旋等待的线程数超过1个,那么轻量级锁会升级为重量级锁
锁膨胀: 竞争的线程想要通过CAS操作获取共享对象的锁的时候,获取不到...就会进行锁膨胀,转化为重量级锁!
自旋锁:
自旋是一种在重量级锁上的优化,并不会让等待的线程进入阻塞状态,而是出于一种
自旋重试的操作!
5.2偏向锁
对象能够认识这个线程,这个线程来了直接把锁交出去,认为对象偏向这个线程.称为偏向锁
在偏向锁状态时通过线程ID来确定当前想要获得对象锁的线程 当有多个线程来竞争这把锁,偏向锁会升级为轻量级锁
使用场景:
只有一个线程来获取资源....单线程