面试官:
说说你对synchronized的理解吧!
我:
synchronized是JDK提供的一个关键字。它可以修饰代码块,此时锁对象需要我们New一个唯一的对象或者提供一个唯一的对象;可以修饰普通方法,此时的锁对象就是this,谁来调用这个方法谁就是锁对象;还可以修饰静态方法,而静态方法只能是当前类对象来调用,所以此时的锁对象就是当前类的Class对象。
在JDK1.5之前synchronized直接就是重量级锁,在1.6优化过后多了几种状态:无锁、偏向锁、轻量级锁、重量级锁。我们先说重量级锁吧。
面试官:
好的。
我:
当一个线程A过来上锁的时候,他会先找到锁对象,而锁对象由三部分组成。第一是对象头,里面有MakeWord 和类Class对象地址;第二是实例变量;第三是对齐补充。而最重要的MakeWord里面就存放着Monitor监视器的地址。我们的线程A就根据该地址去找操作系统,此时就产生了用户态和内核态的切换。找到Monitor对象后我们的线程A就会修改里面的属性:
_count( 非常重要,表示锁计数器,_count = 0表示还没人加锁,_count > 0 表示加锁的次数)
_owner( 非常重要,指向加锁成功的线程,_owner = null 时候表示没人加锁)
此时是没有上锁的_count=0,_owner = null 。线程A就会通过CAS算法比较并替换,修改成_count=1,_owner=自己的线程ID 此时就成功的加上重量级锁了,然后线程A就去执行自己的代码了。
如果A线程执行完了代码就会自动释放锁,将_count和_owner 修改回去,令_count=0,_owner = null。
如果A线程还没有执行完代码,此时还处于加锁的一个状态,又来了一个线程B抢锁。同样的流程,线程B也会找到Monitor对象,但是他就会发现此时的_count属性=1,_owner =线程A的ID,线程B就会开始自旋,自旋的次数和时间由Monitor对象的属性_spinFreq 、spinclock决定。如果所有尝试都结束后还没有获得锁,那么B线程就会进入_entrylist 等待队列,等待线程A释放锁后再次争抢锁。
我:
缓一口气继续说道
关于无锁、偏向锁、轻量级锁其实就是一个锁升级的过程,但是实际并没有真正意义上加锁,因为并没有涉及到用户态和内核态的一个转化。当线程A过来,发现MakeWord 里面锁的标志位是01,里面只有锁对象的hashcode并没有其他线程的线程ID,那么此时就是无锁。线程A加锁,就会利用CAS把自己的线程ID写进去,此时就成了偏向锁。当线程A再次过来抢锁的时候发现MakeWord 里面就是自己的线程ID那么就直接获得了锁。这个时候他的执行效率是比较快的。如果B线程过来抢锁,而A线程还没有释放锁,那么此时锁就升级成了轻量级锁,锁标志是00,然后B线程就会开始自旋,等到A线程释放锁后,线程B再通过CAS去获得锁。如果在高并发的情况下大量线程过来抢锁,然后自旋也会消耗CPU性能,甚至比用户态和内核态的切换更加耗费性能,所以此时就会升级成重量级锁,锁标志是10,多的线程就会进入ErtryList队列等待。
为了拖时间我又接着把synchronized的特性说了
我:
以上就是synchronized锁升级的过程,synchronized可以通过monitor监视器保证原子性,因为只有一个线程能抢到锁嘛。synchronized还可以保证可见性,因为线程在释放锁后一定会把工作内存中的数据写回主存中,而线程上锁后读取数据也一定是从主存中读取最新的数据。synchronized还可以保证有序性,但是这里的有序性和volatile 不一样,synchronized实现有序性是通过操作系统内核互斥锁实现的,退出代码块时一定会刷新变量回主内存,实现的是代码块与代码块之间的有序可见。
这里提到了volatile 面试官后续可能就会问 说一说volatile 。明天继续总结话术
