synchronized
synchronized java中的关键字,主要用于加锁。添加的锁有一下几个特点:
互斥性
同一时间点,只有一个线程可以获得锁,获得锁的线程才能处理被synchronized修饰的代码。
阻塞性
只有获得锁的线程才可以执行被synchronized修饰的代码,没有获得锁的线程只能阻塞,等待所释放。
可重入性
如果一个线程已经获得锁,在未释放之前,再次请求锁的时候,一定可以获得锁的。
synchronized 的用法
synchronized 的使用方法比较简单,主要可以用来修饰方法和代码块。根据其锁定的对象不同,可以用来定义同步方法和同步代码块。
synchronized 的实现原理
对于同步方法,JVM 采用 ACC_SYNCHRONIZED
标记符来实现同步。 对于同步代码块。JVM 采用 monitorenter
、monitorexit
两个指令来实现同步。
同步方法实现原理
方法级的同步是隐式的。同步方法的常量池中会有一个
ACC_SYNCHRONIZED
标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED
,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。
同步代码块实现原理
可以把执行
monitorenter
指令理解为加锁,执行monitorexit
理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,当一个线程获得锁(执行monitorenter
)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit
指令)的时候,计数器再自减。当计数器为 0 的时候。锁将被释放,其他线程便可以获得锁。
在多线程下,synchronized与原子性、有序性、可见性
因为synchronized是一种锁,并且是一种可重入的排它锁,所以他可以保证被他修饰代码同一时间内只能被单一线程访问,并且在进入这段代码前加锁,离开这段代码前解锁。
在某个线程加锁之后,释放之前,其他线程无法获得锁,也就无法执行被锁定的代码,所以这就保证了即使CPU时间片耗尽,也没有其他线程可以执行到被锁定的代码,之后本线程再次获得时间片才行。这就保证了原子性。
另外,在对变量解锁之前,都会把本地内存中变量的值同步到主内存,在加锁之前再从主内存把变量的值读到本地内存。这就保证了,多个线程顺序执行过程中,变量的值是可见的。
另外,根据as-if-serial语义,单线程内是天然有序的,而synchronized通过加锁实现了单线程执行,所以也就具备了有序性。
多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。