在 Java 并发编程中,synchronized 和 ReentrantLock 是两种常用的同步机制,它们都可以用于实现线程安全的代码。不过,它们有着不同的实现方式和特点。本文将介绍 synchronized 和 ReentrantLock 的区别,以及它们各自的优缺点和适用场景。
一、synchronized 和 ReentrantLock 的区别
1.1 实现方式
synchronized 是 Java 中的一种关键字,可以用于方法或代码块上,用于对方法或代码块进行加锁。synchronized 是 JVM 内置的同步机制,由 JVM 实现。在使用 synchronized 时,JVM 会自动进行加锁和释放锁的操作,程序员不需要显式地进行加锁和释放锁的操作。
ReentrantLock 是 Java 中的一个类,实现了 Lock 接口,提供了更加灵活的锁机制。在使用 ReentrantLock 时,程序员需要显式地进行加锁和释放锁的操作。
1.2 性能
synchronized 是 JVM 内置的同步机制,由 JVM 实现,因此它的性能比较高。在 JDK 6 以后,JVM 对 synchronized 进行了优化,使用偏向锁和轻量级锁等技术,使得 synchronized 在绝大多数情况下的性能和 ReentrantLock 相当甚至更高。
ReentrantLock 是使用 Java 代码实现的锁机制,因此它的性能相对于 synchronized 来说稍低一些。但是,ReentrantLock 提供了更加灵活的锁机制,例如可中断、定时和公平的锁等,可以满足更多的需求。
1.3 功能
synchronized 是一种悲观锁,它的特点是默认情况下认为并发访问会导致冲突,因此需要加锁来保证同步。synchronized 在实现上比较简单,但是功能相对比较单一,只能提供基本的同步机制。
ReentrantLock 是一种悲观锁,它提供了更多的灵活性和粒度控制。ReentrantLock 提供了可中断、定时和公平的锁等特性,可以满足更多的需求。此外,ReentrantLock 还支持 Condition 条件变量,可以更加灵活地控制线程的等待和唤醒。
1.4 使用方式
synchronized 的使用方式比较简单,只需要在方法或代码块上加上 synchronized 关键字即可。
public class SynchronizedDemo {
private int count;
public synchronized void increment() {
count++;
}
}
ReentrantLock 的使用方式相对来说比较复杂,需要先创建一个 ReentrantLock 对象,然后在需要加锁的代码块中调用 lock() 方法获取锁,在释放锁的时候需要调用 unlock() 方法。
public class ReentrantLockDemo {
private int count;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
二、synchronized 和 ReentrantLock 的优缺点
2.1 synchronized 的优缺点
优点:
- synchronized 是 JVM 内置的同步机制,使用方便。
- synchronized 在 JDK 6 以后进行了优化,性能表现良好。
- synchronized 可以自动释放锁,避免死锁问题。
缺点:
- synchronized 只提供了基本的同步机制,不支持可中断、定时和公平的锁等特性。
- synchronized 不能满足复杂的同步需求,例如读写锁、公平锁等。
2.2 ReentrantLock 的优缺点
优点:
- ReentrantLock 提供了更加灵活的锁机制,例如可中断、定时和公平的锁等特性。
- ReentrantLock 支持 Condition 条件变量,可以更加灵活地控制线程的等待和唤醒。
- ReentrantLock 可以满足复杂的同步需求,例如读写锁、公平锁等。
缺点:
- ReentrantLock 的使用方式相对较复杂,需要手动进行加锁和释放锁的操作。
- ReentrantLock 的性能相对于 synchronized 稍低一些。
三、synchronized 和 ReentrantLock 的适用场景
synchronized 和 ReentrantLock 都可以用于实现线程安全的代码,但是在实际开发中应该根据具体情况选择合适的同步机制。
synchronized 适用于:
- 简单的同步需求。
- 对性能要求较高的场景。
- 不需要复杂的同步机制,例如读写锁、公平锁等。
ReentrantLock 适用于:
- 复杂的同步需求,例如读写锁、公平锁等。
- 需要可中断、定时和公平的锁等特性的场景。
- 需要更加灵活地控制线程的等待和唤醒的场景。
四、示例代码
下面是一个使用 synchronized 实现线程安全的计数器的示例代码:
public class SynchronizedCounter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
下面是一个使用 ReentrantLock 实现线程安全的计数器的示例代码:
public class ReentrantLockCounter {
private int count;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
在使用 ReentrantLock 实现线程安全的计数器时,需要注意在 getCount() 方法中也需要加锁,否则可能会出现线程安全问题。
总结:
本文介绍了 synchronized 和 ReentrantLock 的区别、优缺点和适用场景。synchronized 是 JVM 内置的同步机制,使用方便,性能表现良好,但是只提供了基本的同步机制,不支持复杂的同步需求。ReentrantLock 提供了更加灵活的锁机制,例如可中断、定时和公平的锁等特性,可以满足更多的需求,但是使用方式相对较复杂,性能稍低一些。在实际开发中应该根据具体情况选择合适的同步机制。