介绍
synchronized是JVM内建的同步机制,提供了互斥性和可见性来保证多线程安全。当某一线程获取到当前锁时,其他线程只能处于阻塞或者等待状态。
用法
可作用于方法上或者代码块中
- 作用于静态方法时,其他synchronized修饰的静态方法或者synchronized(Class)代码段都会被挂起
- 作用与动态方法时,该对象的其他synchronized修饰的动态方法或者synchronized(this)代码段都会被挂起
实现原理
synchronized代码块是由一对monitorenter/monitorexit实现的,Monitor对象是实现同步的基本单元。
如下代码:
public void test(){
synchronized(this){
int i=0;
}
}
反编译后如下:
Monitor的实现
之前的Monitor实现主要依靠操作系统内部的互斥锁来实现,需要进行用户态到内核态的转变,所以是一个重量级的操作锁。
JDK8对此进行了改良,提供了三种实现Monitor的方式,分别为偏斜锁、轻量级锁、重量级锁。
- 偏斜锁(Biased Locking)
没有锁竞争时,默认使用偏斜锁。利用CAS将对象头上的Mark Word部分设置为当前线程ID,但并不是真正意义上的互斥所。好处是由于大部分对象声明周期只会被一个线程锁定,所以偏斜锁无竞争开销。 - 轻量级锁
当出现锁竞争时,也就是另外一个线程试图锁定偏斜锁处理过的对象时,JVM将切换到轻量级锁,利用CAS和Mark Word来试图获取锁,如果重试后扔不成功,则进一步升级为重量级锁。
读写锁
独占锁要么不占,要么独占,这种行为有一定的局限性,比如在一个写少读多的并发情况下,并不适合使用独占锁。
读写锁的原理是读锁时共享,写锁时互斥,运行阶段,如果读锁试图锁定对象,写锁正在被某个线程持有,此时读锁会等待对方操作结束,从而保证数据的正确性。
读写锁的粒度虽然相对较细,但是却增加了比较大的开销。
StampedLock是java8提供的更优于读写锁的类。不仅提供了读锁和写锁,还支持优化读模式。基于一种假设:大多数情况下读和写不会发生冲突,其逻辑先试着读,然后通过validate函数确认是否进入了写模式,
如果没有则可以避免多余的开销;如果进去了,则尝试获取读锁。
自旋锁
在无法获取到锁时,采用轮询体内实现,当循环条件被外界改变时,进入临界区
- 自旋锁在轮询时不会进入阻塞状态,从而避免了线程状态上下文的切换开销
- 轮询会一直占用CPU,如果轮询时间较长则影响CPU执行其他线程,所以适用于锁竞争不激烈的场景。否则还是使用互斥锁