重量级锁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可以禁止偏向锁来提升性能。

1. synchronized关键字在使用层面的理解 synchronized关键字Java中用来实现线程同步的关键字,可以修饰方法和代码块。当线程访问被synchronized修饰的方法或代码块时,需要获取对象的,如果该已被其他线程获取,则该线程会进入阻塞状态,直到获取到为止。synchronized关键字可以保证同一时刻只有一个线程能够访问被定的方法或代码块,从而避免了多线程并发访问时的数据竞争和一致性问题。 2. synchronized关键字在字节码中的体现 在Java代码编译成字节码后,synchronized关键字会被编译成monitorenter和monitorexit指令来实现。monitorenter指令对应获取操作,monitorexit指令对应释放操作。 3. synchronized关键字在JVM中的实现 在JVM中,每个对象都有一个监视器(monitor),用来实现对象。当一个线程获取对象后,就进入了对象的监视器中,其他线程只能等待该线程释放后再去竞争synchronized关键字的实现涉及到对象头中的标志位,包括标志位和重量级标志位等。当一个线程获取后,标志位被设置为1,其他线程再去获取时,会进入自旋等待或者阻塞等待状态,直到标志位被设置为0,即被释放后才能获取。 4. synchronized关键字在硬件方面的实现 在硬件层面,的实现需要通过CPU指令和总线来实现。当一个线程获取时,CPU会向总线发送一个请求信号,其他CPU收到该信号后会进入自旋等待状态,直到被释放后才能获取。总线可以保证多个CPU之间的原子操作,从而保证的正确性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值