浅谈 synchronized

synchronized是Java中的关键字,它是Java中常用的锁,也是Java开发者最先接触的锁,下面就来简要谈一谈它(本文暂不涉及锁优化)

作用

在并发编程中,一般要保证原子性、可见性、有序性,synchronized加持下的代码如何保证这三点:

  1. 原子性:在执行操作之前必须先获得锁,直到执行完才释放,此时不会有其他线程来操作打断;
  2. 可见性:synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到共享内存当中,保证资源变量的可见性;
  3. 有序性:Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,所以同步块中的内容类似于单线程的方式在执行。保证了每个执行的线程的顺序性;

使用

  1. 修饰普通方法
    同步普通方法 ,锁对象默认为this
public synchronized void func() {
    System.out.println("aaa");
}
  1. 修饰静态方法
    同步静态方法 ,锁的对象是当前类的Class对象
public synchronized static void func() {
    System.out.println("bbb");
}
  1. 修饰代码块

代码块中锁实例对象

public void func() {
    //修饰代码块,锁住this
    synchronized (this) {
        System.out.println("ccc");
    }
}

代码块中锁Class对象

public void func() {
    synchronized (Demo.class) {
       System.out.println("ddd");
    }
}

原理

现在编写了一个Demo类,加入同步块和同步方法,代码如图

浅谈 synchronized

将这段代码编译,然后我们使用javap反编译它的字节码,看一看加上关键字后底层究竟有什么不一样

代码块

代码块部分的反编译结果如图

浅谈 synchronized

从图中可以看出synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,编译器在同步代码块的开头和结尾的地方分别加上了monitorenter
monitorexit

当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

Monitor 是基于 C++实现的,是JVM内置的,在Hotspot中由ObjectMonitor实现的。而且每个对象中都内置了一个 ObjectMonitor对象。Monitor 依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的线程同步的。这种实现方式在阻塞和唤醒一个线程时要操作系统切换CPU状态来完成;所以这种状态转换需要耗费处理器时间。在一些情况下,状态转换消耗的时间有可能比用户代码执行的时间还要长。这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”,所以在JDK1.6中开发团队着重优化了synchronized 锁的效率(本文只讨论了重量级锁的情况)。

同步方法

浅谈 synchronized

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取而代之的是 ACC_SYNCHRONIZED 标识,这个标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,在调用的方法有此标志时,执行线程需要先获得monitor锁,然后开始执行方法,方法执行之后再释放monitor锁。所以也是使用的monitor锁;

上一篇:Salesforce LWC学习(三十八) lwc下如何更新超过1万的数据


下一篇:MyBatis 批量修改(update)数据表某字段