synchronized关键字是一个用于同步访问共享资源的机制,它可以确保并发编程中的三个关键要素:原子性、可见性和有序性。

原理

synchronized是Java中的内置锁,通过对象头中的锁状态标志位和锁记录(Lock Record)来实现。每个Java对象都有一个对象头,其中包含了锁状态标志位、指向锁记录的指针等信息。

当一个线程尝试获取某个对象的synchronized锁时,如果该对象的锁状态为无锁状态,则JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),并将其与对象的对象头关联起来。此时,该线程就成功获取了对象的锁,可以执行同步代码块。

如果其他线程也尝试获取该对象的锁,则会被阻塞,直到持有锁的线程释放锁。当持有锁的线程执行完同步代码块后,会将锁状态设置为无锁状态,并唤醒等待队列中的一个线程,使其获取锁并执行同步代码块。

锁升级

在Java 6之前,synchronized的实现是基于重量级锁的,即当线程尝试获取锁失败时,会被阻塞并导致用户态和内核态的切换,性能开销较大。从Java 6开始,JVM对synchronized的实现进行了优化,引入了锁升级的过程,包括无锁状态、偏向锁、轻量级锁和重量级锁四种状态。

无锁状态

对象刚开始时处于无锁状态,也就是没有任何线程持有该对象的锁。

偏向锁

为了减少无竞争情况下的锁开销,JVM引入了偏向锁。当一个线程首次访问同步代码块时,它会在对象头和当前线程的栈帧中记录偏向的线程ID。这样,在后续的执行中,如果仍然是同一个线程访问该同步代码块,JVM就可以判断出来,并允许该线程无锁地执行同步代码。偏向锁实际上是一种延迟加锁的机制,它的目标是消除无竞争情况下的同步原语,进一步提高程序的运行性能。

但是,当有其他线程尝试获取这个偏向锁时,偏向锁就会撤销,并尝试升级为轻量级锁。

轻量级锁

轻量级锁是为了减少线程阻塞而设计的。当偏向锁撤销后,或者多个线程交替执行同步代码块时,锁会升级为轻量级锁。轻量级锁的加锁过程是通过CAS操作实现的,它试图将对象头的Mark Word替换为指向线程栈帧中锁记录的指针。如果成功,则当前线程获得锁;如果失败,说明存在竞争,此时会尝试自旋等待,即让当前线程空转一段时间,然后再次尝试获取锁。

如果自旋等待达到一定的次数仍然没有获取到锁,那么轻量级锁就会升级为重量级锁。

重量级锁

重量级锁是Java中最基础的锁机制,它的实现依赖于操作系统的互斥量(Mutex)。当轻量级锁无法满足性能需求时,会升级为重量级锁。此时,未获取到锁的线程会被阻塞,并进入等待状态,直到持有锁的线程释放锁。由于重量级锁涉及到用户态和内核态的切换,因此它的性能开销相对较大。

重量级锁的实现依赖于底层的 Monitor 机制。每个对象都有一个与之关联的 Monitor,当线程尝试获取重量级锁时,会被放入 Monitor 的入口等待队列中。如果获取锁失败,线程会被阻塞并放入等待队列,直到持有锁的线程释放锁。

面试知识点

同一个类不同方法都有synchronized锁,一个对象是否可以同时访问这些方法?

同一个对象的不同 synchronized 方法不能同时被多个线程访问​​。这是因为 synchronized 修饰实例方法时,锁的是当前对象实例(即 this 对象锁),同一时间只允许一个线程持有该锁。

一个类的static方法加上synchronized之后的锁的影响

静态方法的锁是​​类对象(Class 对象)​​,与实例对象的锁无关。因此,一个线程访问实例同步方法,另一个线程访问静态同步方法时,​​不会互相阻塞​​(因为锁不同)

最后修改:2025 年 06 月 11 日
如果觉得我的文章对你有用,请随意赞赏