【详解】Java中提供了synchronized,为什么还要提供Lock呢?
Java中提供了synchronized,为什么还要提供Lock呢?
在Java多线程编程中,synchronized关键字和Lock接口是实现线程同步的两种主要方式。虽然 synchronized关键字已经能够满足大部分同步需求,但Java并发包(java.util.concurrent)中还是引入了Lock接口。本文将探讨为什么Java需要Lock,以及Lock相比synchronized有哪些优势。
1. synchronized的局限性
1.1 无法灵活控制锁的获取与释放
使用synchronized关键字时,锁的获取和释放是由JVM自动管理的,这虽然简化了开发者的操作,但在某些场景下却显得不够灵活。例如,你可能希望在尝试获取锁失败时执行特定的逻辑,或者希望在多个地方释放锁,这些都无法通过synchronized来实现。
1.2 无法实现公平锁
synchronized关键字默认是非公平的,这意味着如果多个线程同时竞争同一个锁,那么获得锁的顺序是不确定的。有时候,为了保证线程调度的公平性,我们可能需要一个公平锁,而synchronized无法满足这一需求。
1.3 没有锁的超时机制
使用synchronized时,如果一个线程在等待锁的过程中被阻塞,它会一直等待直到锁可用。这可能会导致资源浪费或死锁问题。而Lock接口允许设置尝试获取锁的超时时间,从而避免无限期等待。
1.4 无法实现可中断的锁
当一个线程正在等待获取锁时,如果该线程被其他线程中断,synchronized不会抛出InterruptedException,而是继续等待。而在某些情况下,我们可能希望线程在等待锁时能够响应中断请求,Lock接口则支持这一点。
2. Lock的优势
2.1 更加灵活的锁管理
Lock接口提供了一系列方法来更细粒度地控制锁的获取和释放。例如,lock()、unlock()、tryLock()等方法允许开发者根据具体需求选择合适的锁操作方式。
Lock lock = new ReentrantLock();
lock.lock(); // 获取锁
try {
// 执行临界区代码
} finally {
lock.unlock(); // 释放锁
}
2.2 支持公平锁
ReentrantLock类提供了构造函数参数来指定是否创建一个公平锁。如果设置为true,那么锁将按照线程请求的顺序分配,确保了公平性。
Lock lock = new ReentrantLock(true); // 创建一个公平锁
2.3 锁的超时机制
tryLock(long time, TimeUnit unit)方法允许在尝试获取锁时设置超时时间,如果在指定时间内未能获取到锁,则返回false,这样可以避免无限期等待。
Lock lock = new ReentrantLock();
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 执行临界区代码
} finally {
lock.unlock();
}
} else {
// 处理获取锁失败的情况
}
2.4 可中断的锁
Lock接口支持可中断的锁获取操作,即在等待锁的过程中可以响应中断请求,这可以通过lockInterruptibly()方法实现。
Lock lock = new ReentrantLock();
try {
lock.lockInterruptibly();
try {
// 执行临界区代码
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 处理中断情况
}
2.5 更丰富的功能
除了上述提到的功能外,Lock接口还支持读写锁(ReadWriteLock),允许多个读线程同时访问资源,而写线程独占资源,这对于提高并发性能非常有用。
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
// 读操作
readLock.lock();
try {
// 执行读操作
} finally {
readLock.unlock();
}
// 写操作
writeLock.lock();
try {
// 执行写操作
} finally {
writeLock.unlock();
}
在Java中,synchronized 和 Lock 都是用于实现线程同步的机制,但它们各有优缺点。synchronized 是一种内置的锁机制,使用起来非常方便,但在某些高级场景下可能会显得不够灵活。而 Lock 接口则提供了更多的功能和灵活性,例如可中断的锁、公平锁、读写锁等。
下面通过一个实际应用场景来说明为什么需要 Lock,并给出相应的示例代码。
场景描述
假设我们有一个银行账户类 BankAccount,需要支持多线程环境下的存款和取款操作。我们需要确保这些操作是线程安全的。
使用 synchronized 的实现
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public synchronized void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println(Thread.currentThread().getName() + " deposited " + amount + ", new balance: " + balance);
}
}
public synchronized void withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " withdrew " + amount + ", new balance: " + balance);
}
}
public synchronized double getBalance() {
return balance;
}
}
使用 Lock 的实现
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccountWithLock {
private double balance;
private final Lock lock = new ReentrantLock();
public BankAccountWithLock(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
lock.lock();
try {
if (amount > 0) {
balance += amount;
System.out.println(Thread.currentThread().getName() + " deposited " + amount + ", new balance: " + balance);
}
} finally {
lock.unlock();
}
}
public void withdraw(double amount) {
lock.lock();
try {
if (amount > 0 && balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " withdrew " + amount + ", new balance: " + balance);
}
} finally {
lock.unlock();
}
}
public double getBalance() {
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}
}
为什么需要 Lock?
- 可中断的锁:
Lock支持可中断的锁获取,即在等待获取锁时可以被中断。这在某些情况下非常有用,例如长时间等待锁的线程可以被其他线程中断。
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 操作
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire the lock within 5 seconds");
}
- 公平锁:
ReentrantLock可以设置为公平锁,确保线程按照请求锁的顺序获取锁。
private final Lock lock = new ReentrantLock(true); // 公平锁
- 读写锁:
Lock接口还提供了ReadWriteLock,允许多个读线程同时访问,但写线程独占访问。这对于读多写少的场景非常有用。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class BankAccountWithReadWriteLock {
private double balance;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public BankAccountWithReadWriteLock(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
lock.writeLock().lock();
try {
if (amount > 0) {
balance += amount;
System.out.println(Thread.currentThread().getName() + " deposited " + amount + ", new balance: " + balance);
}
} finally {
lock.writeLock().unlock();
}
}
public void withdraw(double amount) {
lock.writeLock().lock();
try {
if (amount > 0 && balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " withdrew " + amount + ", new balance: " + balance);
}
} finally {
lock.writeLock().unlock();
}
}
public double getBalance() {
lock.readLock().lock();
try {
return balance;
} finally {
lock.readLock().unlock();
}
}
}
通过这些示例,我们可以看到 Lock 提供了更多的灵活性和高级功能,使得在复杂多变的并发环境中能够更好地控制和管理线程同步。在Java中,synchronized 和 Lock 都是用于实现线程同步的机制,但它们各有特点和适用场景。下面详细介绍为什么Java在提供了 synchronized 之后还需要提供 Lock,并附上一些示例代码。
synchronized 的局限性
- 不可中断:当一个线程等待
synchronized 锁时,它是不能被中断的。如果需要在等待锁的过程中处理中断,synchronized 就无法满足需求。 - 非公平锁:
synchronized 是非公平锁,这意味着它不保证等待时间最长的线程会优先获取锁。这可能会导致某些线程长时间无法获取到锁。 - 无法尝试加锁:使用
synchronized 时,线程必须无条件地等待锁。如果需要在尝试加锁失败时执行其他操作,synchronized 无法实现。 - 锁绑定方法:
synchronized 锁是与方法或代码块绑定的,无法单独操作锁。
Lock 的优势
- 可中断:
Lock 提供了 lockInterruptibly 方法,允许线程在等待锁时被中断。 - 公平锁:
Lock 可以实现公平锁,确保等待时间最长的线程优先获取锁。 - 尝试加锁:
Lock 提供了 tryLock 方法,允许线程在尝试加锁失败时执行其他操作。 - 灵活的锁操作:
Lock 提供了更多的锁操作方法,如 newCondition 方法可以创建多个条件变量。
示例代码
使用 synchronized
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
使用 Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int count = 0;
private final 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();
}
}
}
更多 Lock 的高级用法
可中断的锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockExample {
private final Lock lock = new ReentrantLock();
public void doSomething() throws InterruptedException {
lock.lockInterruptibly();
try {
// 执行操作
} finally {
lock.unlock();
}
}
}
尝试加锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final Lock lock = new ReentrantLock();
public void doSomething() {
if (lock.tryLock()) {
try {
// 执行操作
} finally {
lock.unlock();
}
} else {
// 处理加锁失败的情况
System.out.println("Failed to acquire lock");
}
}
}
公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private final Lock lock = new ReentrantLock(true); // true 表示公平锁
public void doSomething() {
lock.lock();
try {
// 执行操作
} finally {
lock.unlock();
}
}
}
通过这些示例代码,可以看到 Lock 相比 synchronized 提供了更多的灵活性和功能,适用于更复杂的并发控制场景。
- 点赞
- 收藏
- 关注作者
评论(0)