1

synchronized为什么是可重入的
简单理解就是当前线程已经持有了对象锁,当前线程可以继续访问

synchronized底层使用的是lock+cmpxchg
synchronized是非公平锁,当一个线程要获取锁时,先试图插队,如果占用线程释放了锁,其他线程没有获取锁,那么当前线程就可以获取锁,如果锁被其他线程占用,那么加入到waitset中,排队,排队的时候不能获取锁,只能等前面所有的等待线程获取锁之后才能获取

锁升级过程
无锁:没有任何线程持有对象锁
偏向锁:当只有一个线程访问的时候,在markword中记录偏向线程的id
轻量级锁:当出现锁竞争的时候,会采用自旋形式获取锁,一般会自旋10次,当自旋结束还没获取到锁,升级为重量级锁。
重量级锁:需要像os申请mutex互斥量

锁只能升级,不能降级,hotspot是这么实现的。
轻量级锁,只是在用户态自旋,不需要切换到内核态,但是轻量级锁浪费cpu
重量级锁,需要切换到内核态申请mutex资源,浪费时间
所以轻量级锁比重量级锁快

锁消除和锁粗化
锁消除:虚拟机对不存在共享数据的锁进行消除,
锁消除需要虚拟机开启逃逸分析
锁粗化:多个加锁、解锁操作连在一起,扩大一个范围更大的锁

        //锁消除
        public void test() {
            Object object = new Object();
            synchronized(object) {
                //锁只对单次方法调用起作用,其实没有意义
            }
        }
        
        //锁粗化
        Object lock = new Object();
        public void add() {
            for(int i = 0; i< 100; i++) {
                 synchronized(lock) {
                 }
            }
        }
        //粗化后
        public void add() {
            synchronized(lock) {
                for(int i = 0; i< 100; i++) {
                
                }
            }
        }

image.png
001表示无锁,101表示偏向锁,00表示轻量级锁,01 表示重量级锁,11 表示GC

自旋锁在jdk1.4.2中引入,使用-XX:+UseSpinning来开启,在jdk1.6中默认开启,并引入了自适应自旋锁
自旋锁升级为轻量级锁:有线程自旋超过10次,-XX:PreBlockSpin,或者自旋超过cpu核数一半,1.6之后,加入了自适应自旋,由jvm来控制
升级为重量级锁:jvm是运行在用户态的进程,需要像操作系统申请mutex资源,cpu从ring3用户态切换到ring0内核态,线程挂起进入waitset,等待操作系统调度,返回用户空间。

synchronized是重入锁,重入的次数必须记录,因为要解锁几次必须要对应
锁重入次数,偏向锁和轻量级锁记录到LockRecord中,重量级锁记录到ObjectMonitor中

为什么有自旋锁,还需要重量级锁?
自旋消耗cpu资源,如果锁时间过长,或者有自旋线程较多,cpu资源会被大量浪费,直接升级为重量级锁,进入等下队列,节约cpu资源

偏向锁是否一定比自旋锁效率高?
不一定,在明确知道多线程竞争的情况下,偏向锁会设计锁撤销操作,这时候直接使用自旋锁效率较高,
偏向锁有个延时,默认是4s,-XX:BiasedLockingStartupDelay=0,因为jvm启动的时候,会默认启动一些线程,里面会涉及很多同步操作,启动的时候肯定会有竞争,如果使用偏向锁,就会设计锁撤销和锁升级过程,效率较低。

image.png


一只鱼
49 声望1 粉丝

« 上一篇
ThreadState
下一篇 »
锁状态标识位