【详解】Java中提供了synchronized,为什么还要提供Lock呢?

举报
皮牙子抓饭 发表于 2025/10/27 21:51:26 2025/10/27
【摘要】 Java中提供了​​synchronized​​,为什么还要提供​​Lock​​呢?在Java多线程编程中,​​synchronized​​关键字和​​Lock​​接口是实现线程同步的两种主要方式。虽然​​ synchronized​​关键字已经能够满足大部分同步需求,但Java并发包(​​java.util.concurrent​​)中还是引入了​​Lock​​接口。本文将探讨为什么Jav...

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​​?

  1. 可中断的锁Lock 支持可中断的锁获取,即在等待获取锁时可以被中断。这在某些情况下非常有用,例如长时间等待锁的线程可以被其他线程中断。
if (lock.tryLock(5, TimeUnit.SECONDS)) {
    try {
        // 操作
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("Could not acquire the lock within 5 seconds");
}
  1. 公平锁ReentrantLock 可以设置为公平锁,确保线程按照请求锁的顺序获取锁。
private final Lock lock = new ReentrantLock(true); // 公平锁
  1. 读写锁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​​ 的局限性

  1. 不可中断:当一个线程等待 ​​synchronized​​ 锁时,它是不能被中断的。如果需要在等待锁的过程中处理中断,​​synchronized​​ 就无法满足需求。
  2. 非公平锁:​​synchronized​​ 是非公平锁,这意味着它不保证等待时间最长的线程会优先获取锁。这可能会导致某些线程长时间无法获取到锁。
  3. 无法尝试加锁:使用 ​​synchronized​​ 时,线程必须无条件地等待锁。如果需要在尝试加锁失败时执行其他操作,​​synchronized​​ 无法实现。
  4. 锁绑定方法:​​synchronized​​ 锁是与方法或代码块绑定的,无法单独操作锁。

​Lock​​ 的优势

  1. 可中断:​​Lock​​ 提供了 ​​lockInterruptibly​​ 方法,允许线程在等待锁时被中断。
  2. 公平锁:​​Lock​​ 可以实现公平锁,确保等待时间最长的线程优先获取锁。
  3. 尝试加锁:​​Lock​​ 提供了 ​​tryLock​​ 方法,允许线程在尝试加锁失败时执行其他操作。
  4. 灵活的锁操作:​​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​​ 提供了更多的灵活性和功能,适用于更复杂的并发控制场景。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。