面试题答案
一键面试synchronized 自动获取和释放锁机制
- 获取锁:
当一个线程进入被
synchronized
修饰的同步代码块或方法时,会自动尝试获取对象的锁。如果锁可用,线程会获取锁并进入同步区域执行代码;如果锁不可用,线程会被阻塞,进入等待队列,直到锁被释放。 例如,对于实例方法:
public class SynchronizedExample {
public synchronized void synchronizedMethod() {
// 同步代码
}
}
这里,synchronizedMethod
方法被synchronized
修饰,线程调用该方法时会尝试获取this
对象的锁。
对于静态方法:
public class StaticSynchronizedExample {
public static synchronized void staticSynchronizedMethod() {
// 同步代码
}
}
此时,线程调用staticSynchronizedMethod
方法时会尝试获取StaticSynchronizedExample.class
类对象的锁。
对于同步代码块:
public class SynchronizedBlockExample {
private final Object lock = new Object();
public void synchronizedBlock() {
synchronized (lock) {
// 同步代码
}
}
}
线程进入同步代码块时会尝试获取lock
对象的锁。
2. 释放锁:
当线程执行完同步代码块或方法,或者在同步代码块或方法中抛出异常时,会自动释放锁。这意味着,无论代码以何种方式正常结束或异常结束,锁都会被释放,等待队列中的其他线程有机会获取锁。
Lock 手动获取和释放锁机制
- 获取锁:
使用
Lock
接口实现类(如ReentrantLock
)时,线程需要调用lock()
方法手动获取锁。如果锁不可用,线程会被阻塞,直到获取到锁。 例如:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
public void lockMethod() {
lock.lock();
try {
// 同步代码
} finally {
lock.unlock();
}
}
}
在上述代码中,线程调用lockMethod
方法时,通过lock.lock()
尝试获取锁。如果锁不可用,线程会在此处阻塞。
2. 释放锁:
获取锁后,必须手动调用unlock()
方法释放锁。为确保锁一定会被释放,通常将unlock()
方法放在finally
块中,这样即使在同步代码中抛出异常,锁也能被正确释放。
差异在实际应用场景中的影响
- 灵活性:
Lock
提供了更灵活的锁获取方式。例如,tryLock()
方法可以尝试获取锁,如果锁不可用立即返回false
,而不是像synchronized
那样一直阻塞。这在某些场景下(如尝试获取锁多次,若都失败则执行其他逻辑)非常有用。Lock
还支持公平锁(构造ReentrantLock
时传入true
),按照线程等待的顺序分配锁,而synchronized
是非公平锁,在高并发场景下,可能会导致某些线程长时间等待。
- 异常处理:
synchronized
在发生异常时会自动释放锁,不需要额外处理。- 使用
Lock
时,由于手动释放锁,若在同步代码中发生异常而未在finally
块中正确释放锁,会导致锁永远无法释放,其他线程一直阻塞。所以使用Lock
时需要更严谨的异常处理。
- 性能:
- 在低竞争场景下,
synchronized
和Lock
性能相近。 - 在高竞争场景下,
Lock
的性能优势可能更明显,因为它可以使用非公平锁提高吞吐量,并且在锁的获取和释放上有更细粒度的控制。但这也依赖于具体的应用场景和代码实现。
- 在低竞争场景下,