1. 锁的获取与释放机制差异
- 获取锁的过程:
- synchronized:是基于JVM层面的内置锁,当一个线程进入同步代码块(使用
synchronized
修饰的代码块或方法)时,会自动获取锁。如果锁不可用,线程会进入阻塞状态,等待锁的释放。
- ReentrantLock:通过调用
lock()
方法获取锁。如果锁不可用,线程同样会等待,但ReentrantLock
提供了更灵活的获取锁方式,比如可以尝试获取锁(tryLock()
方法)。
- 是否可中断:
- synchronized:当一个线程尝试获取
synchronized
锁时,如果锁被其他线程持有,该线程会一直阻塞,无法被中断,除非持有锁的线程释放锁或者抛出异常。
- ReentrantLock:支持可中断的锁获取操作。通过调用
lockInterruptibly()
方法,在等待获取锁的过程中,如果线程被中断,会抛出InterruptedException
异常并停止等待。
- 锁的公平性:
- synchronized:是非公平锁,即锁的分配是随机的,新进入的线程有可能比等待时间较长的线程先获取到锁。
- ReentrantLock:默认也是非公平锁,但可以通过构造函数
ReentrantLock(boolean fair)
设置为公平锁。公平锁会按照线程等待的先后顺序分配锁,等待时间最长的线程会优先获取锁。
- 释放锁的时机和方式:
- synchronized:当同步代码块或方法执行完毕(正常结束或抛出异常),JVM会自动释放锁。无需手动释放。
- ReentrantLock:需要手动调用
unlock()
方法释放锁,且必须在获取锁的代码块内的最后执行。如果没有正确释放锁,可能会导致死锁等问题。同时,ReentrantLock
支持在锁获取的代码块内多次调用lock()
方法(可重入特性),相应地也需要调用相同次数的unlock()
方法来释放锁。
2. 适用场景分析
- 适合使用ReentrantLock的场景:
- 需要可中断的锁获取操作:例如在处理一些长时间运行的任务时,如果需要在等待锁的过程中能够响应中断信号,就可以使用
ReentrantLock
的lockInterruptibly()
方法。
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockExample {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
lock.lockInterruptibly();
try {
System.out.println("Thread 1 acquired the lock");
// 模拟长时间运行的任务
Thread.sleep(2000);
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("Thread 1 was interrupted while waiting for the lock");
}
});
Thread thread2 = new Thread(() -> {
lock.lock();
try {
System.out.println("Thread 2 acquired the lock");
// 中断thread1
thread1.interrupt();
} finally {
lock.unlock();
}
});
thread1.start();
thread2.start();
}
}
- **需要公平锁**:在一些对公平性要求较高的场景,比如多个线程按照顺序执行任务,公平锁可以确保等待时间最长的线程优先获取锁,避免线程饥饿问题。
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private static final ReentrantLock fairLock = new ReentrantLock(true);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired the fair lock");
} finally {
fairLock.unlock();
}
}).start();
}
}
}
- 适合使用synchronized的场景:
- 简单的同步场景:当代码逻辑比较简单,对锁的功能需求不复杂时,使用
synchronized
可以使代码更简洁,因为它不需要手动获取和释放锁,由JVM自动管理。
public class SynchronizedExample {
private static final Object monitor = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (monitor) {
System.out.println("Thread 1 entered synchronized block");
// 同步代码逻辑
}
});
Thread thread2 = new Thread(() -> {
synchronized (monitor) {
System.out.println("Thread 2 entered synchronized block");
// 同步代码逻辑
}
});
thread1.start();
thread2.start();
}
}
- **与其他Java内置特性结合**:由于`synchronized`是Java内置的关键字,与Java的其他特性(如对象的内置锁机制)紧密结合,在一些依赖这些特性的场景下使用更方便。例如在实现单例模式时,使用`synchronized`修饰静态方法来确保线程安全的实例创建。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}