简要概述synchronized底层原理

 

synchronized与可见性

JMM关于synchronized的两条语义规定了:

  • 线程加锁前:需要将工作内存清空,从而保证了工作区的变量副本都是从主存中获取的最新值。
  • 线程解锁前;需要将工作内存的变量副本写回到主存中。

大概流程:清空线程的工作内存->在主存中拷贝变量副本到工作内存->执行完毕->将变量副本写回到主存中->释放锁。

所以synchronized能保证共享变量的可见性,而实现这个流程的原理也是通过插入内存屏障,和关键字volatile相似。

synchronized与有序性

因为synchronized是给共享变量加锁,即使用阻塞的同步机制,共享变量只能同时被一个线程操作,所以JMM不用像volatile那样考虑加内存屏障去保证synchronized多线程情况下的有序性,因为CPU在单线程情况下是保证了有序性的。

所以synchronized修饰的代码,是保证了有序性的。

synchronized与原子性

同样因为synchronized是给共享变量加锁了,以阻塞的机制去同步,在对共享变量进行读/写操作的时候是原子性的。

所以synchronized修饰的代码,是能保证原子性的。

 

synchronized同步代码块是通过加monitorenter和monitorexit指令实现的

每个对象都有个监视器锁(monitor) ,当monitor被占用的时候就代表对象处于锁定状态,而monitorenter指令的作用就是获取monitor的所有权,monitorexit的作用是释放monitor的所有权,这两者的工作流程如下:

  • 1.如果monitor的进入数为0,则线程进入到monitor,然后将进入数设置为1,该线程称为monitor的所有者。
  • 2.如果是线程已经拥有此monitor(即monitor进入数不为0),然后该线程又重新进入monitor,则将monitor的进入数+1,这个即为锁的重入。
  • 3.如果其他线程已经占用了monitor,则该线程进入到阻塞状态,知道monitor的进入数为0,该线程再去重新尝试获取monitor的所有权。
  • 4.monitorexit:执行该指令的线程必须是monitor的所有者,指令执行时,monitor进入数-1,如果-1后进入数为0,那么线程退出monitor,不再是这个monitor的所有者。这个时候其它阻塞的线程可以尝试获取monitor的所有权。
  • 5.加有synchronized关键字的方法,常量池中比普通的方法多了个ACC_SYNCHRONIZED标识,JVM就是根据这个标识来实现方法的同步。当调用方法的时候,调用指令会检查方法是否有ACC_SYNCHRONIZED标识,有的话线程需要先获取monitor,获取成功才能继续执行方法,方法执行完毕之后,线程再释放monitor,同一个monitor同一时刻只能被一个线程拥有。

总结:

        synchronized的语义底层是通过一个monitor的对象完成,其实wait、notiyf和notifyAll等方法也是依赖于monitor对象来完成的,这也就是为什么需要在同步方法或者同步代码块中调用的原因(需要先获取对象的锁,才能执行),否则会抛java.lang.IllegalMonitorStateException的异常

ps:

synchronize func() 锁方法锁的是整个对象,两个线程只能一个进入该方法执行同步方法,,对于非同步方法则没有影响

static synchronize func()方法锁的是 class对象,所以相应的对象也会被锁,非同步方法不会有影响