目录
乐观锁和悲观锁是在并发编程中用于处理数据一致性和并发控制的两种不同的策略。
在Java中,乐观锁的思想是,假设并发的事务之间很少产生冲突,因此不需要阻塞其他事务的执行。在读取数据时,并不会加锁,而是在更新数据时才进行检查。乐观锁的实现通常使用CAS指令(Compare And Swap)操作。CAS是一种无锁算法,它通过比较并交换的方式,实现对共享变量的原子更新。CAS操作包括三个操作数:被操作的内存值V、预期的值A和新值B。当且仅当V的值等于A时,CAS才会将V的值更新为B,否则不进行任何操作。
乐观锁的实现思路如下:
Java中常见的乐观锁实现方式有:
注意:乐观锁的实现通常通过版本号(Versioning)或时间戳(Timestamp)来实现。当一个事务要更新一条数据时,先读取数据的当前版本号,然后执行更新操作。在提交事务时,检查数据库中的版本号是否与事务开始时读取的版本号相匹配,如果匹配则提交成功,否则则说明在提交之前有其他事务已经对数据进行了修改,此时需要回滚并重新尝试。
乐观锁的优点是并发性能较高,不需要阻塞其他事务的执行,但缺点是在并发冲突较多的情况下,需要频繁地回滚和重试,可能会影响性能。
在Java中, 悲观锁的思想是,假设并发的事务之间经常产生冲突,因此在操作数据时会先将其锁定,以防止其他事务对数据进行修改。悲观锁通常使用synchronized关键字、ReentrantLock等锁机制来实现。这些锁机制都可以保证同一时间只有一个线程可以访问共享资源,其他线程需要等待锁的释放才能继续执行。
悲观锁的实现思路如下:
Java中常见的悲观锁实现方式有:
悲观锁的优点是保证数据的一致性,不会出现并发冲突的情况,但缺点是会导致并发性能较低,因为其他事务需要等待锁的释放才能执行。
选择使用乐观锁还是悲观锁,取决于具体的业务场景和性能需求。如果并发冲突较少,并发性能是关键考量因素,那么可以选择乐观锁;如果并发冲突较多,数据一致性是首要考量因素,可以选择悲观锁。当然,也可以根据具体的情况采用两者的结合策略,灵活运用。
下面是一个包含乐观锁和悲观锁合在一起实现线程安全的账户转账操作的代码示例,
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- public class Main {
- private String accountId;
- private AtomicInteger balance;
- private Lock lock;
-
- public Main(String accountId, int initialBalance) {
- this.accountId = accountId;
- this.balance = new AtomicInteger(initialBalance);
- this.lock = new ReentrantLock();
- }
-
- // 使用悲观锁实现转账操作
- public void transferPessimistic(Main targetAccount, int amount) {
- synchronized (this) {
- synchronized (targetAccount) {
- if (this.balance.get() >= amount) {
- this.balance.addAndGet(-amount); // 当前账户减去转出金额
- targetAccount.balance.addAndGet(amount); // 目标账户加上转入金额
- System.out.println("悲观锁:账户" + this.accountId + " 向账户" + targetAccount.accountId + " 转账:" + amount);
- } else {
- System.out.println("悲观锁:余额不足,无法完成转账");
- }
- }
- }
- }
-
- // 使用乐观锁实现转账操作
- public void transferOptimistic(Main targetAccount, int amount) {
- while (true) {
- int sourceBalance = this.balance.get(); // 获取当前账户余额
- int targetBalance = targetAccount.balance.get(); // 获取目标账户余额
- if (sourceBalance >= amount) {
- if (this.balance.compareAndSet(sourceBalance, sourceBalance - amount)) { // 原账户减去转出金额
- if (targetAccount.balance.compareAndSet(targetBalance, targetBalance + amount)) { // 目标账户加上转入金额
- System.out.println("乐观锁:账户" + this.accountId + " 向账户" + targetAccount.accountId + " 转账:" + amount);
- break;
- }
- }
- } else {
- System.out.println("乐观锁:余额不足,无法完成转账");
- break;
- }
- }
- }
-
- public static void main(String[] args) {
- Main account1 = new Main("A123", 1000);
- Main account2 = new Main("B456", 2000);
-
- // 创建多个线程进行转账操作
- Thread thread1 = new Thread(() -> {
- account1.transferPessimistic(account2, 300);
- });
-
- Thread thread2 = new Thread(() -> {
- account2.transferOptimistic(account1, 500);
- });
-
- thread1.start();
- thread2.start();
- }
- }
在这个示例中,我们创建了两个账户对象 account1 和 account2,并使用悲观锁和乐观锁来实现了转账操作。transferPessimistic 方法使用悲观锁,在转账过程中加锁了两个账户对象,并根据账户余额判断是否能够完成转账;transferOptimistic 方法使用乐观锁,利用 AtomicInteger 类型的原子操作来保证账户余额的一致性。
在 main 方法中,我们创建了两个线程,并分别调用账户对象的转账方法进行转账操作,模拟了多线程环境下的并发转账。