Java中的乐观锁实现及其应用场景

在并发编程中,如何在多个线程间共享资源是一个核心问题。传统的做法是使用各种锁机制(如synchronized或ReentrantLock)来确保数据一致性。然而,锁机制往往会导致线程等待和性能损耗。为此,我们引入了“乐观锁”的概念,它能够有效的提高并发性能。

一、什么是乐观锁?

乐观锁是一种乐观的并发控制机制,它假设多个线程操作同一个资源时,冲突是偶发事件。每次线程读取数据时,认为其他线程不会修改该数据,只有在更新数据时,才会检查是否有冲突。

乐观锁与传统的锁不同,它不阻塞线程执行。在多线程环境下,乐观锁通过版本号的机制来控制并发的更新操作。如果版本号未发生变化,则允许更新;否则拒绝操作并通知线程重新尝试。

二、乐观锁的实现原理

乐观锁的典型实现方式是通过版本号来控制并发。具体步骤如下:

1.每个记录都带有一个版本号(version)。

2.线程读取记录时,同时读取其版本号。

3.线程在更新数据时,检查当前记录的版本号是否与之前读取的一致。如果一致,则更新成功并增加版本号;否则,说明数据已经被其他线程修改,当前操作失败,需要重新读取数据。

三、乐观锁的代码实现

接下来,我们通过一个多线程操作共享数据的简单示例,来展示如何在Java中使用乐观锁。我们将使用版本号机制,确保数据的正确性。

1.数据类

首先,我们需要一个Data类,用于存储需要操作的数据以及对应的版本号。为了确保线程安全,版本号使用AtomicInteger进行自增。

 package com.wyx.interview.optimisticLock;
 ​
 import java.util.concurrent.atomic.AtomicInteger;
 ​
 /**
  * @Description Time tries all things
  * @Author wyx
  * @Date 2024/9/9
  **/
 public class Data {
     // 使用 AtomicInteger 保证版本号的线程安全
     private static AtomicInteger version = new AtomicInteger(1);
     // 真实数据
     private static String data = "实现一个乐观锁";
 ​
     // 获取当前版本号
     public static int getVersion() {
         return version.get();
     }
 ​
     // 更新版本号
     public static void updateVersion() {
         version.incrementAndGet();  // 版本号自增1
     }
 ​
     // 获取当前数据
     public static String getData() {
         return data;
     }
 ​
     // 设置新数据
     public static void setData(String newData) {
         data = newData;
     }
 }
 ​
2.线程类

每个线程会读取数据并尝试更新。在线程运行过程中,我们会检查当前版本号,只有版本号与预期相同,才会进行数据的写操作。

 package com.wyx.interview.optimisticLock;
 ​
 /**
  * @Description Time tries all things
  * @Author wyx
  * @Date 2024/9/9
  **/
 public class OptimisticThread extends Thread {
     private int version;  // 线程预期的版本号
     private String newData;  // 线程要写入的新数据
 ​
     // 构造函数
     public OptimisticThread(String name, int version, String newData) {
         super(name);  // 设置线程名
         this.version = version;
         this.newData = newData;
     }
 ​
     @Override
     public void run() {
         int retryCount = 3;  // 允许的重试次数
         while (retryCount > 0) {
             // 1. 读取当前数据
             String currentData = Data.getData();
             System.out.println("线程" + getName() + ",获得的数据版本号为:" + Data.getVersion());
             System.out.println("线程" + getName() + ",预期的数据版本号为:" + version);
             System.out.println("线程" + getName() + "读取数据完成=========data = " + currentData);
 ​
             // 2. 检查版本号是否匹配
             if (Data.getVersion() == version) {
                 synchronized (OptimisticThread.class) {
                     // 3. 再次检查版本号,并更新数据
                     if (Data.getVersion() == version) {
                         Data.setData(newData);  // 更新数据
                         Data.updateVersion();  // 更新版本号
                         System.out.println("线程" + getName() + "写数据完成========new data = " + newData);
                         return;  // 成功,结束线程
                     }
                 }
             } else {
                 // 版本号不匹配,进行重试
                 System.out.println("线程" + getName() + "获得的数据版本号不匹配,重试...");
             }
             retryCount--;  // 重试次数减少
         }
 ​
         // 如果重试次数耗尽
         System.out.println("线程" + getName() + "重试次数用完,操作失败!");
     }
 }
 ​

3.测试类

最后我们创建多个线程,模拟多线程操作共享数据的场景。

 
package com.wyx.interview.optimisticLock;
 ​
 /**
  * @Description Time tries all things
  * @Author wyx
  * @Date 2024/9/9
  **/
 public class Test {
     public static void main(String[] args) {
         // 创建多个线程,尝试并发修改数据
         for (int i = 1; i <= 2; i++) {
             new OptimisticThread(String.valueOf(i), 1, "新数据" + i).start();
         }
     }
 }

输出示例:

线程1,获得的数据版本号为:1
线程2,获得的数据版本号为:1
线程1,预期的数据版本号为:1
线程1读取数据完成=========data = 实现一个乐观锁
线程1写数据完成========new data = 新数据1
线程2,预期的数据版本号为:1
线程2读取数据完成=========data = 实现一个乐观锁
线程2获得的数据版本号不匹配,重试...
线程2,获得的数据版本号为:2
线程2,预期的数据版本号为:1
线程2读取数据完成=========data = 新数据1
线程2获得的数据版本号不匹配,重试...
线程2,获得的数据版本号为:2
线程2,预期的数据版本号为:1
线程2读取数据完成=========data = 新数据1
线程2获得的数据版本号不匹配,重试...
线程2重试次数用完,操作失败!

Process finished with exit code 0
四、乐观锁的应用场景
1.高并发场景

乐观锁适合用于高并发场景,多个线程同时读写时,冲突概率不高的情况。与悲观锁相比,乐观锁不会阻塞线程,提升了系统的并发性能。

2.无强一致性需求的系统

乐观锁适用于对数据强一致性要求不高的系统。如果每次读写数据冲突的可能性较大,悲观锁可能是更好的选择。

3.数据库中的乐观锁

在数据库操作中,常常使用乐观锁来控制并发。例如,在UPDATE操作时,利用SQL语句中的WHERE子句加上版本号检查,只有在版本号匹配时才能更新成功。

五、总结

乐观锁通过版本号机制,减少了线程之间的锁竞争,从而提高了并发性能。通过文本的示例代码,我们了解了如何在Java中实现一个简单的乐观锁机制。乐观锁并不是万能的,它适用于冲突较少的场景,如果并发冲突较多,则可能导致频繁的重试,进而影响性能。

感谢你看到最后,最后再说两点~
  ①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
  ②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~

感兴趣的可以关注公众号一起学习,我会不定期发布学习和一些有意思的见闻。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月夜奇术师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值