如何实现多线程
我们在使用多线程访问资源时,为了防止冲突,我们一般会怎么做呢?
首先会想到,用synchronized呗.
那synchronized原理是啥呢?加锁呗.
这时候如果是面试的话,面试官就要开始拷打了:synchronized做了哪些优化?
直接搬结论,java1.6后,java的开发人员针对锁机制进行了优化,增加了偏向锁、轻量级锁.
锁的介绍
啥是锁
不是哥么,你锁来锁去,咩是锁啊?
锁就是一个对象,就举synchronized的例子,修饰普通方法的时候,锁的就是实例对象,修饰静态方法的时候,锁的是class对象,修饰代码块的时候,锁的是自定义对象
对象的组成部分
你说是对象就是对象,那我普通对象和锁对象有啥区别啊?
还真有~
首先我们先来看,对象有哪些组成部分:对象头、实例数据、对齐填充字节
其中对象头又由Mark Word(对象运行时的数据)、Klass Pointer(指向对象类型的指针)、数组长度(对象不为数组就没有这一部分)
判断一个对象 是不是锁、是谁的锁 的信息就存放在Mark Word里面,具体而言长啥样?
上图
注意哈,这里的这个图不是整个对象头,而是说每种情况的对象头各自的组成部分,那你可能要问,无锁态有hashCode,分代年龄这些东西,怎么偏向锁、轻量级锁、重量级锁有的就没了?
诶,问到点上了.
简而言之,就是偏向锁、轻量级锁就是没有hashcode,想有hashcode的话,会升级为重量级锁,将hashcode存储在ObjectMonitor里面
那分代年龄呢,轻量级锁会存储在lock record里面,重量级锁的分代年龄在ObjectMonitor里面
锁的升级过程
直接速通
偏向锁
- 当线程第一次访问共享资源时,使用CAS操作,将自己的线程id,赋值给Mark Word中,同时修改Mark Word里面的偏向锁标志位为1
- 后续线程访问资源时,只需要判断(不需要使用CAS操作)对象头中的线程id和自己的线程id是否一致,一致就OK. 否则,使用CAS修改偏向锁,或者撤销偏向锁,将锁升级为轻量级锁
轻量级锁
- 首次升级为轻量级锁时:先在线程自己的栈帧中创建锁记录,把Mark Word的信息复制到锁记录中,将锁记录中的owner指针指向当前线程,修改Mark Word为锁记录的指针
- 而后,每次线程访问资源,都使用CAS操作试图修改owner指针指向自己,若成功则可访问资源,否则会自旋重试,失败到达一定次数(默认10次),会升级为重量级锁
重量级锁
- 首次升级为重量级锁时:将指向Monitor的指针赋值给Mark Word,同时修改锁标志位为10
- 而后每次回去查看Monitor的owner是否为自己(可重入),若是则可以获取锁并访问资源,否则说明有其他线程持有锁,该线程阻塞
重量级锁的底层
- 底层是一个Monitor,和对象绑定,里面有三个重要部分:Owner、Entry List、Wait Set
- Owner:当前持有锁的线程,有且仅有一个
- Entry List:因获取锁失败而阻塞的线程
- Wait Set:调用了wait方法处于等待状态的线程