进程和线程
进程:一个启动正在运行的程序称之为进程。线程:是操作系统能够进行运算调度的最小单位。它包含在进程中,是进程执行的最小单元,是一个程序的运行轨迹。
一、synchronized
底层实现原理:synchronized 底层字节码文件是通过monitor指令控制线程,同步代码块儿在获取线程的开始部位添加一个monitorrenter指令,在结束部位添加一个monitorexit指令,入下图所示(代码可以通过javap命令编译查看)。为什么最后会有monitorexit命令,是为了防止中间出现异常,而最后能给正常退出,避免造成死锁。
synchronized 是可重入的锁,可重入锁的意思就是,同一个线程进入同步代码块儿是被允许的,并且在父子类之间也是可重入的。底层原理是当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
锁优化
在一个线程要想获取锁就需要向系统内核申请资源,在这个过程是非常消耗资源的,应该是为了避免过度的申请消耗,从而在对象做了手脚;一个java对象由对象标记,类型指针,真实数据,内存补齐四部分组成:
对象标记也称Mark Word字段,存储当前对象的一些运行时数据。
类型指针,JVM根据该指针确定该对象是哪个类的实例化对象。
真实数据自然是对象的属性值。
内存补齐,是当数据不是对齐数的整数倍的时候,进行调整,使得对象的整体大小是对齐数的整数倍方便寻址。典型的以空间换时间的思想。
Mark Word
提到锁优化就提到Mark Word,他是用于存储对象自身运行时的数据,如hashcode,GC分代年龄,锁状态标志位,线程持有的锁,偏向线程ID
锁优化的阶段 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
无锁:就是没有加锁的状态
偏向锁:针对的是锁不存在竞争,每次仅有一个线程来获取该锁,为了提高获取锁的效率,因此将该锁偏向该线程,提升性能。
轻量级锁:偏向锁考虑的是不存在多个线程竞争同一把锁,而轻量级锁考虑的是,多个线程不会在同一时刻来竞争同一把锁。
重量级锁:重量级锁描述同一时刻有多个线程竞争同一把锁。
锁升级过程:
1、首先检查对象头是否为可偏向状态,锁标志是否设置成了1;2、如果对象头是可偏向状态检查线程id是否是自己的线程id,如果是自己的线程id则直接进入同步代码块儿;3、如果不是自己的线程id那么会通过CAS尝试获取锁,这时候成为自旋锁,自旋10次以后如果还没有获取到锁资源,则升级为重量级锁
锁细化:同步代码块儿中存在少量代码,无需枷锁的代码移到同步代码块外面
锁粗花:一个循环中存在加锁操作时,可以将加锁操作提到循环外面执行,一次加锁代替多次加锁,提升性能
volatile
volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。
volatile特性
1、保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见
volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现。
内存屏障,又称内存栅栏,是一个 CPU 指令。
在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM 为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序
2、禁止指令重排,
为了性能优化,JMM 在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序。JMM 提供了内存屏障阻止这种重排序。
Java 编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。
3、volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性
4、应用场景
对变量的写操作不依赖于当前值。
该变量没有包含在具有其他变量的不变式中。
参考:https://www.jianshu.com/u/a5d93d03e01c