重量级锁synchronized关键字

重量级锁synchronized关键字

一、synchronized的作用

1.可见性:synchronized关键字在线程解锁前必须把变量同步回主内存,在线程加锁前必须从主内存中重新获取最新的值。

2.原子性:synchronized关键字保证每一个时刻只能一个线程持有该同步块,来保证其原子性。

3.有序性:synchronized通过一个时刻只允许一个线程执行同步块,即使指令进行重排序仍旧不影响结果。它不会禁止指令重排序,为保证指令重排序最好的方法是禁止处理器优化和指令重排序。

4.可重入行:synchronized和ReentrantLock都是可重入锁,当一个线程试图操作一个由其它线程持有的对象锁的临界资源时,会处于阻塞状态,但是同一个线程可再次持有,这种情况属于重入锁,即可重复申请锁。

二、锁原理

​ jvm中synchronized是通过监视器锁Monitor来实现代码的同步,同一时刻只允许一个线程调用synchronized关键字加锁的代码。同步代码块是使用monitorenter和monitorexit指令来实现,同步方法通过标记ACC_SYNCHRONIZED来隐式实现。同步代码块调用时会判断代码块是否有线程在进行使用,如果有且不是使用线程,则加入对象头的等待队列,否则将正在执行的代码数量加1,退出后则减1。当线程计数为0时则允许其它线程调用。同步方法调用时是通过同步判断标记来进行调用。

​ 如下代码是一个未使用synchronized修饰的代码

public class SynchronizedTest {
    public void myPrint(){
        for(int i=0; i<100;i++){
            System.out.print(i);
        }
    }
}

​ 其反编译结果如下

在这里插入图片描述

​ 如下代码是增加synchronized修饰的代码。

public class SynchronizedTest {
    public void myPrint(){
        Object lock = new Object();
        for(int i=0; i<100;i++){
            synchronized(lock){
                System.out.print(i);
            }
        }
    }
}

​ 经过反编译javap -v xxx.class后如下

在这里插入图片描述

​ 通过对比我们能发现反编译结果中添加了monitorenter和monitorexit,代表进入Monitor监视器,和退出Monitor监视器。

public class SynchronizedTest {

    public synchronized void myPrint(){
        for(int i=0; i<100;i++){
            System.out.print(i);
        }
    }
}

方法锁反编译后后效果

在这里插入图片描述

三、java对象头

​ HotSpot虚拟机将对象分头为两部分,一部分用于存储运行时数据,如哈希码(HashCode)、GC分代年龄(Generational GC Age)等,官方将其称为标记位“Mark Word",它是实现轻量级锁和偏向锁的关键。另一部分用于存储指向方法区对象的指针,如果是数组对象的化,还会有一个额外的部分用于存储数组长度。

四、锁优化

1、自旋锁和自适应锁

​ 当虚拟机中有一个线程A去正在使用共享数据,这时另外一个线程B过来需要使用该共享数据。如果线程B发现共享资源已经被使用而直接放弃尝试获取锁且释放cpu资源,但线程A在该线程放弃持有锁时很快就释放了共享数据的锁,这时线程B又要去获取cpu资源然后获取共享数据,这个过程很浪费资源。对于这种情况我们只需要让线程B进行空循环(自旋),就可以不进行cpu资源的释放和重新占用,这项技术就是所谓的自旋锁。但是自旋次数太多又会浪费处理器资源就需要控制自旋次数,自旋次数默认位10,这时可以使用-XX:PreBlockSpin来更改自旋次数。JDK1.6引入自适应锁,这时自旋时间不再固定,而是通过前一次的自旋时间和锁的持有状态来决定,即自适应锁。

2、锁消除

​ 虚拟机运行时对一些代码要求同步,但是被检测到不可能存在共享数据时对锁进行消除。判断依据逃逸分析。

3、锁粗化

​ 在进行代码编写时,我们会遵循同步块范围控制的尽量小的原则,只在共享数据作用域进行同步。大部分情况下,这样的原则是对的,但是如果一些列操作都对同一对象反复加锁,甚至在循环体中进行加锁操作,这样即使没有线程竞争频繁的加锁和解锁也会损耗大量的性能,这时会将加锁动作进行粗化,例如上面第一段代码将锁放在循环体外执行。

4、轻量级锁

​ 在代码进入同步块时,如果此时同步块没有被锁定(锁的标记位为01状态),虚拟机会在当前线程的栈帧中建立一个锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。然后尝试使用CAS操作尝试将对象的Mark Word进行更新,如果更新成功,那么这个线程就持有了这个对象的锁,并且对象Mark Word的锁标志位将转变位00.如果更新失败虚拟机首先会检查对象是否指向当前线程,如果是则直接进行更新,否则说名锁已被其它线程抢占则说明两个线程持有对象,将锁膨胀位重量级锁。

5、偏向锁

​ 如果轻量级锁在无竞争的情况下使用CAS操作去使用的互斥量,那偏向锁就是无竞争的情况下将整个同步消除,连CAS操作也不做了。当锁对象第一次被线程获取的时候,虚拟机将这个对象头的标志位设为01,即偏向模式,同时使用CAS操作把取到的这个锁的ID记录在对象的Mark Word之中,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机不再进行任何同步操作。-XX:UseBiasedLocking可以禁止偏向锁来提升性能。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页