### 顶尖的Java多线程、锁、内存模型面试题详解
#### Synchronized相关问题解析
**问题一:Synchronized用过吗,其原理是什么?**
`Synchronized` 是 Java 语言提供的一种同步机制,用于确保共享资源在多线程环境下的安全访问。它的原理主要是通过 JVM 在运行时为被 `synchronized` 关键字修饰的代码块添加 `monitorenter` 和 `monitorexit` 字节码指令来实现。
当线程执行到 `monitorenter` 指令时,会尝试获取对象的锁。如果该对象未被锁定或当前线程已经拥有该对象的锁,那么锁的计数器将增加。当线程执行到 `monitorexit` 指令时,锁的计数器减少,当计数器为零时,锁被释放。如果线程尝试获取锁失败,则会被阻塞,直到锁被释放。
`Synchronized` 锁通过在对象头设置标志来实现,这样可以有效地管理对象的锁定状态。
**问题二:你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?**
“锁”是 `Synchronized` 用来控制并发的关键概念。它通过 `monitorenter` 和 `monitorexit` 指令来标识要锁定和解锁的对象。根据 `Synchronized` 的使用方式,可以确定锁的对象:
1. **明确指定了锁对象:** 如 `Synchronized(变量名)` 或 `Synchronized(this)`,这里的锁对象就是指定的对象。
2. **未明确指定锁对象:**
- 如果 `Synchronized` 修饰的是非静态方法,则锁对象是该方法所属的对象实例。
- 如果 `Synchronized` 修饰的是静态方法,则锁对象是该类的类对象。
值得注意的是,当一个对象被锁住时,该对象内部所有用 `Synchronized` 修饰的方法都会受到影响而被阻塞,除非它们是非 `Synchronized` 方法。
**问题三:什么是可重入性,为什么说 `Synchronized` 是可重入锁?**
可重入性是指同一个线程能够多次进入同一段同步代码的能力。这可以防止线程在调用自己时发生死锁。例如,如果一个类的一个同步方法调用了另一个同步方法,那么第二个方法调用不会导致第一个方法的锁被释放,而是允许线程继续执行。
`Synchronized` 的可重入性体现在 `monitorenter` 指令中。当线程尝试获取一个已经被自身持有的锁时,锁的计数器将递增,而不是拒绝线程再次获取锁。这意味着同一个线程可以多次进入由 `Synchronized` 定义的同步代码块。
**问题四:JVM对Java的原生锁做了哪些优化?**
早期的 `Synchronized` 实现完全依赖于操作系统提供的互斥锁,这涉及到从用户态到内核态的转换,代价非常高昂。为了提高性能,现代的 JVM 对 `Synchronized` 做了大量的优化:
1. **自旋锁:** 在阻塞线程之前,让线程在一个短时间内自旋等待,以避免不必要的系统调用。
2. **锁的分级:**
- **偏向锁(Biased Locking):** 当一个对象被首次锁定时,JVM 使用偏向锁。在这种模式下,对象头被设置为指向当前持有锁的线程。如果之后没有其他线程试图获取该锁,那么该线程就可以无锁地访问对象。这减少了无竞争情况下的锁获取和释放成本。
- **轻量级锁:** 当存在竞争时,JVM 尝试使用轻量级锁。它仍然不涉及内核态的操作,而是通过 CAS 操作来实现锁的获取和释放。
- **重量级锁:** 当轻量级锁不足以解决问题时,JVM 将升级到重量级锁,这涉及到内核态的转换。重量级锁是最慢但最可靠的锁实现。
这些优化策略提高了 `Synchronized` 的效率,使其成为更灵活、更高效的同步工具。
**问题五:为什么说 `Synchronized` 是非公平锁?**
`Synchronized` 被称为非公平锁是因为它在锁的分配过程中不遵循公平原则。这意味着当锁被释放时,并不是等待时间最长的线程优先获得锁,而是任意一个准备好的线程都有可能获得锁。这种设计是为了提高性能,因为在实际应用中,大多数情况下锁的竞争并不激烈。然而,这也可能导致某些线程长时间无法获取到锁的情况,即所谓的不公平现象。