package com.fh.interview; /** * @author * @create 2018-05-27 下午4:40 **/ public class JMMTest { /** * 源代码--编译器指令重排序(编译器)--指令并行重排序(处理器)--内存指令重排序(处理器) * * JMM下对程序员提供的上层规则 * * 1、程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。 2、监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。 3、volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。 4、传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。 5、start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。 6、join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。 7、程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。 8、对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。 */ public void test(){ } /** * synchronized * 如果锁的是类对象的话,尽管new多个实例对象, * 但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系。 * javac JMMTest.java 编译出来JMMTest.class * 编译之后,切换到SynchronizedDemo.class的同级目录之后, * 然后用javap -v JMMTest.class查看字节码文件: * * * JMM核心:happens-before和内存抽象模型 * * happens-before实现原理: * 释放锁的时候会将值刷新到主内存中, * 其他线程获取锁时会强制从主内存中获取最新的值 * * 无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态 * synchronized的优化:CAS和对象头 * cas问题: * ABA问题,自旋时间过长,只能保证单个原子变量 * 对象头:Mark world * * * 偏向锁:对象头和栈针的锁记路中保存线程ID,修改对象头中的信息 * 轻量级锁:复制对象头中的mark world到栈中的锁记录中,尝试修改对象头中的信息改为指向 * 锁记录中的指针 * */ public synchronized void test1(){ } public static void main(String[] args) { synchronized (JMMTest.class){ } method(); } private static void method(){ } }
JMM
1、什么是内存模型
在特定的操作协议下,对特性的内存或高速缓存进行读写访问的过程抽象
2、Java的内存模型
工作内存--可以类比为栈内存
主内存--可以类比为堆内存
3、内存间交互的命令和从主内存读数,从工作内存写数以及必须满足的规则
lock--主内存变量,标识为线程独占
unlock--主内存变量,锁定的变量释放
read--主内存变量,主-->工
load--工作内存变量,接受主内存的变量
use--工作内存变量,传递给执行引擎
assign--工作内存变量,接受执行引擎的变量
store--工作内存变量,工-->主
write--主内存变量,接受工作内存的变量
变量从主内存拷贝到工作内存:read load
变量从工作内存同步到主内存:store write
read/load store/write 不能单一出现 线程不能丢弃最近的assign操作 不能没有原因的从工作内存同步到主内存 新的变量只能在主内存中诞生
一个变量同一时刻只允许一条线程对他lock操作
对变量lock后,需要重新load 或者assign已初始化 没有lock,不能unlock 对变量lock前,必须同步回主内存
4、对volatile的特殊规则
1、每次使用变量前,需要工作内存从主内存中刷新值
2、每次修改变量后,必须立即同步到主内存中
3、修饰的变量不会被指令重排序优化
5、内存模型的特征
1、原子性
2、可见性
3、有序性
6、先行放生原则
1、程序次序规则 按照控制流规则,写在前面的先行发生写在后面的
2、管程锁定规则 先解锁才能加锁
3、volatile变量规则 写操作先行发生于后面对变量的读操作
4、线程启动 start先行发生线程内的所有操作
5、线程终止 线程操作线程发生于线程终止操作
6、线程终端 先调用中断,才能检测到中断
7、对象中断 对象初始化先行发生于finalize的调用
8、传递性 a->b b->c a->c
synchronized
1、一般如何如何使用
主要是添加到方法或者代码块上,锁定实例对象或者Class对象
2、对象头信息
对象头包含Mark Word 和klass point ,在64位机上,指针压缩的情况下,占用96bit,Mark Word 64,
Mark Word 结构
3、加锁过程
1、当对象不可偏向,即偏向标志位0,A线程要进入同步代码块时,判断偏向标志不可偏向,在当前栈帧中创建lock record空间,将对象的Mark wordc拷贝到lock record中(还会加一些东西),然后cas替换对象中的Mark Word
指向栈帧中的锁记录,当A线程没有释放锁,B线程尝试进入同步代码块的时候,锁膨胀为重量级锁,Mark Word 替换为指向重量锁的指针,当A线程释放锁的时候,cas失败,会唤醒B线程
2、当对象可偏向,即偏向标志位1,A线程要进入同步代码块时,判断偏向标志可偏向,会将当前线程的ID添加到Mark Word ,当A线程释放锁,B线程进入到代码块的时候,锁会撤销成无锁,然后膨胀成轻量级锁,一般锁是不可
重偏向的 批量重定向和批量撤销都需要重复动作到达一定的阀值才会发生
当锁存在竞争的时候,会有一个自适应自旋的过程,根据上一次获取锁的时候,动态调整本次是否需要自旋和自旋的次数
4、如果调用了wait ,锁会直接进入到重量级锁
如果对象已经调用了hashcode,则对象不能够偏向了