1.什么是用户态和内核态
用户态:用户执行自己的程序的空间。
内核态:当需要进行监听键盘输入事件,读取文件,IO操作等都会由用户态切换到内核态。内核态管理着计算机的硬件资源。
2.用户态切换到内核态
当用户态需要访问内核管理的资源时,内核提供一个系统调用机制,在cpu中的实现叫做陷阱指令。
用户态程序将需要操作系统提供的服务数据存放在寄存器或者栈中,随后执行陷阱指令。
进入内核态,执行程序指定的指令,这些指令是用户态访问不了的,读取内存的数据参数,执行指定的服务。
重置cpu为用户态,返回执行服务的结果。
3.在synchronized没有优化前的问题
synchronized在没有被优化前就是一个重量级锁,加在对象,方法,代码块上解决并发问题。
重点在于,多个线程进入同步代码块,争夺锁资源,线程A获得了锁,线程B没有抢到锁,那么线程B就会进入阻塞状态,直到线程A释放掉锁,然后线程B被唤醒,重新抢锁,直到拿到锁。
在线程B进入阻塞状态和恢复到运行状态,这会导致cpu由用户态切换到内核态。上文叙述的用户态内核态之间的切换是十分消耗资源的。
所以synchronized在没有优化前,性能不太高。
4.锁优化
偏向锁:
在一个线程的情况下,重量级锁是完全没有必要的,所以当一个线程进入同步代码块,对象头会写入这个线程的id,并将偏向锁位置为1,表示目前是偏向锁。当下一次,此线程再次进入同步代码块,只需要判断此线程id和对象头的id是否一致,一致就直接进入同步代码块。
轻量级锁:
在多个线程发生资源竞争时,不会直接膨胀到重量级锁,而是在一个线程拿到锁对象后,其余线程进行自旋操作,在一个循环里不断尝试获得锁,也就是CAS操作,CAS是乐观锁的一种实现,是在CPU层面实现的。CAS操作不会发生用户态内核态的上下文切换,也就避免了上下文切换带来的开销,在一定程度上比重量级锁的效率要高,所以叫做轻量级锁,也就是自旋锁。
重量级锁:
拿不到锁的线程不断自旋虽然比上下文切换要好些,但是同样会有开销,当自旋到一定次数后,轻量级锁就会膨胀为重量级锁。拿不到锁的线程会进入阻塞,直到锁被释放,线程被唤醒,重新进行抢锁。
5.对象结构
Java对象和数组对象在JVM堆内存的结构图
Java对象的结构
对象头:存放CG年龄,锁对象,哈希码,偏向锁id等元数据。
实际数据:对象数据存放的真正位置。
对齐填充:因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。起占位的作用。
6.对象头结构
mark_word:
klass: 记录指向对象所属类的指针
length(数组对象才有):记录数组的长度。
7.mark_word结构
mark_word的结构图
注:unuserd cms_free占位。
8.锁升级的过程
无锁状态:
mark_word记录对象hashcode,分代年龄,偏向锁位置为0,表示没有偏向锁,锁位为01。
偏向锁状态:
如果目前只有一个线程进入同步代码块,没有发生资源争夺,当前线程在栈中创建Displaced_lock_record,复制mark_word到锁记录中,并且owner指向对象头。将偏向锁位置为1,表示是偏向锁,锁状态为01。
JVM开启偏向锁会有4s的延迟,当对象是无锁状态或者经过延迟后,没有其他线程进行竞争,那么对象就会进入无锁可偏向状态。
如果JVM关闭了偏向锁,那么对象就是不可偏向状态,这时候有线程尝试获得锁,那么会直接升级到轻量级锁。
轻量级锁:
其他线程进入同步代码块,进行资源争夺。当前是偏向锁的线程被挂起,撤销偏向锁,将锁记录中的mark_word还原。偏向锁的撤销需要等待全局安全点Safe Point
(安全点是 jvm为了保证在垃圾回收的过程中引用关系不会发生变化设置的安全状态,在这个状态上会暂停所有线程工作),在这个安全点会挂起获得偏向锁的线程。所有竞争线程在栈中创建Displaced_lock_record,复制mark_word到锁记录中,JVM尝试CAS将lock_record中的owner指向对象头,对象头将指针指向锁记录,将锁位置为00。
情况一:线程B进来,线程A执行同步代码块完毕,且对象是可重偏向的,那么线程A撤销偏向锁,线程B获得偏向锁。
情况二:线程B进来,线程A还没执行完同步代码块,撤销偏向锁,升级为轻量级锁。
情况三:线程B进来,线程A执行同步代码块完毕,但是对象是不可重偏向的,那么会撤销偏向锁,升级为轻量级锁。
情况四:线程A死亡,对象是可重偏向的,线程B获得偏向锁。
重量级锁:
当自旋锁自旋到一定次数后,轻量级锁会膨胀为重量级锁,且不可降级。当前获得锁的线程被挂起,创建monitor对象指向对象,mark_word指向monitor对象,锁位置位10。其他线程直接进入阻塞状态,等待锁释放,唤醒后重新争夺锁。