面试官:
描述一下你对synchronized的理解。
小羊:
首先我们先说synchronized的用法:
1.修饰实例方法
public synchronized V merge() {}
2.修饰静态方法
public synchronized static void write() {
}
3.修饰代码块
private void writeObject(java.io.ObjectOutputStream s)
throws IOException {
synchronized (this) {
}
}
以下内容不记得是来源于哪里了,我以前的笔记里记的。
偏向锁: MarkWord存储的是偏向的线程ID;
轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针;
重量锁:MarkWord存储的是指向堆中的monitor对象的指针
如下所示,锁主要存在四种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,他们随着竞争的情况逐渐升级。
(1)、对象最开始是无锁状态的001;
(2)、偏向锁,顾明思义,他会偏向于第一个访问的线程。第一个使用该对象的线程对该对象加synchronized操作后便是是偏向锁101,默认以后只有这个线程使用该对象,若果偏向锁开关没开的话这时候加锁的便是轻量级锁00了;这样设计偏向锁的的好处是:如果自始至终使用该对象的线程真的只这有一个的话,很明显几乎就没有什么额外的开销,性能极高,毕竟需要加锁的对象是少数,适合大多数的对象的情况。偏向锁是不可逆的,一旦某个线程加上了偏向锁就不会回到无锁状态了。
(3)、当再来第二个线程通过CAS加锁,这时候如果能加锁成功,是轻度的竞争,对象就变为轻量级锁00。因为此时是新来的线程,对象头中存入的是之前的偏向线程,只要新来的线程和之前的偏向线程不是同一个线程就会变为轻量级锁,就会消除对象上的偏向锁;
(4)、如果步骤三中的的对象正在拿着轻量级锁,此时再来新的线程竞争的话,如果能够通过CAS获取到锁便还好还是轻量级锁;如果竞争拿不到锁,便通过自旋或者自适应自旋来获取,如果能成功便还是轻量级锁;如果自旋不成功或者有大量的线程竞争锁资源,便会升级为重量级锁。
(5)、 升级成为重量级锁的过程就是当一个线程给一个对象上重量级锁的时候会尝试获取它的锁资源,然后这个锁资源会关联一个monitor监视器对象,将monitor的锁定状态+1,并且将monitor的指针写入到一个对象头中,然后将锁标志位改为10。然后加锁时会执行一个monitorenter方法。在释放锁时会执行monitorexit方法。
当其他线程来尝试获取锁资源时,会发现monitor监视器中锁的状态不为0,那么就会在monitor的监视状态下去等待这个锁资源。
然后synchronized是一个可重入锁,一个线程重复获取锁资源每获取一次会给锁状态+1,每释放一次会使monitor锁状态-1,直到锁状态为0那么释放锁资源。(这个是同步代码块的原理)
无锁:
一个对象最少占16个字节,如果包含栈中的引用,那么最少占20个字节。
头信息占12个字节:
1)markword 8字节 用来存储锁信息,哈希码,gc信息
2)classpoint 4字节
实例数据:没有就是0个字节
对齐填充:用于确保对象的大小能够被8整除,如果不能被整除那么填充到能被整除。
markword如图所示:
大家好,我是小羊炒饭。感谢大家的关注,有任何问题可以提出,我们一起学习共同进步。从今天开始每周至少会有一篇纵向学习和分析java代码和框架的文章。公司的前辈告诉我,想涨工资人情>技术,我不懂人情,所以我想深耕技术,希望有一天能涨工资哈哈。