了解 synchronized 和 ReentrantLock
synchronized
1)是 Java 内置的关键字,用于提供对象或方法级别的同步机制。
2)它能够确保在同一时刻只有一个线程可以执行 synchronized 修饰的方法或代码块。
3)不需要手动释放锁,当 synchronized 方法或代码块执行完毕后,锁自动释放。
4)锁的获取和释放是隐式的,为开发者提供了便捷的线程同步机制。
5)不支持公平锁、可中断锁等高级功能。
ReentrantLock
1)属于 java.util.concurrent.locks
包下的一个类,提供了比 synchronized 更广泛的锁操作。
2)必须手动声明锁的获取和释放(通过 lock()
和 unlock()
方法)。
3)支持许多高级功能,如可中断的锁等待、公平锁、锁续租等。
4)支持可中断的锁等待( lockInterruptibly()
)
5)提供了条件变量(Condition)支持,允许分开管理线程间的通信。
简单示例
synchronized 示例
public class SynchronizedExample {
public synchronized void syncMethod() {
// 同步方法体
}
public void syncBlock() {
synchronized (this) {
// 同步代码块
}
}
}
ReentrantLock 示例
1)非公平锁
javaCopy codeimport java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock(); // 默认非公平锁
public void lockMethod() {
lock.lock();
try {
// 受保护的代码区
} finally {
lock.unlock();
}
}
}
2)公平锁
javaCopy codeimport java.util.concurrent.locks.ReentrantLock;
public class FairReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock(true); // 启用公平锁
public void fairLockMethod() {
lock.lock();
try {
// 公平锁保证按请求锁的顺序获取锁
} finally {
lock.unlock();
}
}
}
加深记忆的例子
1)synchronized:可以比喻为家里的卫生间一次只能一个人使用。当有人在使用时,其他人必须等待,直到里面的人用完出来。这个过程完全自动化,使用者不需要手动锁门或解锁。
2)ReentrantLock:则像是有一个可控的门禁系统。你可以决定谁能进入,是否允许排队,以及何时结束使用。它提供了更多控制,比如允许尝试获取资源而不是无限等待,或者按照到达顺序来获取资源。
使用场景
1)synchronized:适用于简单的同步需求,如小规模的并发控制,或者不需要可中断锁、公平锁等高级功能的场景。
2)ReentrantLock:适用于复杂的并发控制场景,特别是需要利用条件变量、公平锁、可中断锁等高级功能时。
公平锁和非公平锁
公平锁
公平锁是指多个线程按照请求锁的顺序来获取锁,遵循先来先得的原则。如果锁已被其他线程占用,新来的线程就会进入等待队列。只有当队列中的前面所有线程都获取到锁并释放后,该线程才能获得锁。这种机制虽然保证了锁分配的绝对公平性,但是会导致更大的线程切换开销和整体吞吐量的降低,因为频繁的线程调度会消耗大量的系统资源。
优点:
1)确保无饥饿发生,每个线程最终都可以获取到锁。
2)适用于事务处理等对执行顺序敏感的场景。
缺点:
1)效率较低,因为要保证按照请求顺序获得锁,可能导致线程切换和等待的增加。
2)可能会造成锁的平均获取时间延长。
非公平锁
非公平锁是指多个线程获取锁的顺序并不是按照请求锁的顺序,允许"插队"。新请求的锁可能会直接获得锁,即使有其他线程正在等待。这种方式不保证等待的线程能够按照请求顺序获得锁,但是在多数情况下能够减少唤醒和阻塞的次数,从而提高系统的吞吐量。
优点:
1)效率更高,因为减少了队列的调度,可以更快地获得锁,提高了程序的响应速度和吞吐量。
2)在非竞争或低竞争的环境下,性能优于公平锁。
缺点:
1)可能会导致线程饥饿,即某些线程可能会很长时间获取不到锁。
2)在高度竞争的环境下,线程可能会不断地尝试获取锁,从而增加CPU的负担。