1.作用
synchronized是java提供的一种最基本的锁,可重入的非公平锁,主要用在多线程并发中,当要求某种操作在同一时间只能由一个线程处理时,就需要加锁,否则就会造成数据的错乱。
2.锁类型
synchronized可用在static代码块,静态方法,实例方法以及方法块中,不同的地方所用的锁是不一样的。
3.实现的原理
public class SynTest {
private final Object lock = new Object();
private int a = 1;
public void test() {
synchronized (lock) {
a++;
}
}
}
以这段简单的代码为例,我们用任意实例对象Object来加的锁,编译成.class文件后,我们再看一下代码(比较多,只截取了重要的部分):
TRYCATCHBLOCK L0 L1 L2 null
TRYCATCHBLOCK L2 L3 L2 null
L4
LINENUMBER 8 L4
ALOAD 0
GETFIELD com/game/xiangxuemytest/thread/SynTest.lock : Ljava/lang/Object;
DUP
ASTORE 1
MONITORENTER
L0
LINENUMBER 9 L0
ALOAD 0
DUP
GETFIELD com/game/xiangxuemytest/thread/SynTest.a : I
ICONST_1
IADD
PUTFIELD com/game/xiangxuemytest/thread/SynTest.a : I
L5
LINENUMBER 10 L5
ALOAD 1
MONITOREXIT
在编译后的代码中可以看到 ** MONITORENTER,MONITOREXIT**,两个指令,这两个指令就是同步的关键。当线程执行代码前,需要先执行monitorEnter,哪个线程获取了monitor对象,便可执行代码,否则线程会进入阻塞状态。当代码运行完成后,会执行monitorExit,将对象释放。
注意:synchronized是一种可重入锁。
4.锁的状态
锁在哪里:在同步的时候是获取对象的monitor,即获取到对象的锁。那么对象的锁怎么理解?无非就是类似对对象的一个标志,那么这个标志就是存放在Java对象的对象头。Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位。32为JVM Mark Word默认存储结构为(注:摘自《java并发编程的艺术》):
锁的状态:在jdk1.5之前,synchronized在实现上是一种很重的锁,会导致线程的阻塞;在1.5及之后,将其做了优化,将锁的状态分成了4类:无锁、偏向锁、轻量级锁、重量级锁,这几个状态会随着锁的竞争,逐步升级,锁可以升级但不能降级,也就意味着一旦升级后,即可不存在竞争了,那锁也无法降级了。对象的MarkWord变化为下图
偏向锁:HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,当此线程来获取对象锁时,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果通过了,此线程就会执行代码,如果没通过,则锁存在着线程竞争,则升级为轻量级锁。
轻量级锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。当有线程来获取锁时,会尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。当自旋达到一定次数时依然未得到锁时,说明线程竞争比较多,该轻量级锁就会升级为重量级锁。
重量级锁:也就是jdk1.5之前的实现方式了,当有线程竞争锁失败时,会进入阻塞状态,而这中间上下文的切换,会加大cpu的开销。
5.锁的比较
网友评论