展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > 使用Redis实现分布式锁的方法

使用Redis实现分布式锁的方法

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具
立即下载

啥是分布式锁?我们学习Java都知道锁的概念,例如基于JVM实现的同步锁synchronized,以及jdk提供的一套代码级别的锁机制lock,我们在并发编程中会经常用这两种锁去保证代码在多线程环境下运行的正确性。但是这些锁机制在分布式场景下是不适用的,原因是在分布式业务场景下,我们的代码都是跑在不同的JVM甚至是不同的机器上,synchronized和lock只能在同一个JVM环境下起作用。所以这时候就需要用到分布式锁了。例如,现在有个场景就是整点抢消费券(疫情的原因,支付宝最近在8点、12点整点开放抢

啥是分布式锁?

我们学习 Java 都知道锁的概念,例如基于 JVM 实现的同步锁 synchronized,以及 jdk 提供的一套代码级别的锁机制 lock,我们在并发编程中会经常用这两种锁去保证代码在多线程环境下运行的正确性。但是这些锁机制在分布式场景下是不适用的,原因是在分布式业务场景下,我们的代码都是跑在不同的JVM甚至是不同的机器上,synchronized 和 lock 只能在同一个 JVM 环境下起作用。所以这时候就需要用到分布式锁了。

例如,现在有个场景就是整点抢消费券(疫情的原因,支付宝最近在8点、12点整点开放抢消费券),消费券有一个固定的量,先到先得,抢完就没了,线上的服务都是部署多个的,大致架构如下:

 使用Redis实现分布式锁的方法

 所以这个时候我们就得用分布式锁来保证共享资源的访问的正确性。

回到顶部

为什么要用分布式锁嗯?

假设不使用分布式锁,我们看看 synchronized 能不能保证?其实是不能的,我们来演示一下。

下面我写了一个简单的 springboot 项目来模拟这个抢消费券的场景,代码很简单,大致意思是先从 Redis 获取剩余消费券数,然后判断大于0,则减一模拟被某个用户抢到一个,然后减一后再修改 Redis 的剩余消费券数量,打印扣减成功,剩余还有多少,否则扣减失败,就没抢到。整块代码被 synchronized 包裹,Redis 设置的库存数量为50。

//假设库存编号是00001private String key = "stock:00001";
@Autowiredprivate StringRedisTemplate stringRedisTemplate;/**
 * 扣减库存 synchronized同步锁*/@RequestMapping("/deductStock")public String deductStock(){    synchronized (this){        //获取当前库存
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(key));        if(stock>0){            int afterStock = stock-1;
            stringRedisTemplate.opsForValue().set(key,afterStock+"");//修改库存
            System.out.println("扣减库存成功,剩余库存"+afterStock);
        }else {
            System.out.println("扣减库存失败");
        }
    }    return "ok";
}

然后启动两个springboot项目,端口分别为8080,8081,然后在nginx里配置负载均衡

upstream redislock{
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}
server {
    listen       80;
    server_name  127.0.0.1;
    location / {
        root   html;
        index  index.html index.htm;
        proxy_pass http://redislock;    }
}

 然后用jmeter压测工具进行测试

 使用Redis实现分布式锁的方法

 使用Redis实现分布式锁的方法

然后我们看一下控制台输出,可以看到我们运行的两个web实例,很多同样的消费券被不同的线程抢到,证明synchronized在这样的情况下是不起作用的,所以就需要使用分布式锁来保证资源的正确性。

 使用Redis实现分布式锁的方法

回到顶部

如何用Redis实现分布式锁?

在实现分布式锁之前,我们先考虑如何实现,以及都要实现锁的哪些功能。

1、分布式特性(部署在多个机器上的实例都能够访问这把锁)

2、排他性(同一时间只能有一个线程持有锁)

3、超时自动释放的特性(持有锁的线程需要给定一定的持有锁的最大时间,防止线程死掉无法释放锁而造成死锁)

4、...

基于以上列出的分布式锁需要拥有的基本特性,我们思考一下使用Redis该如何实现?

1、第一个分布式的特性Redis已经支持,多个实例连同一个Redis即可

2、第二个排他性,也就是要实现一个独占锁,可以使用Redis的setnx命令实现

3、第三个超时自动释放特性,Redis可以针对某个key设置过期时间

4、执行完毕释放分布式锁

科普时间

Redis Setnx 命令

Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值

语法

redis Setnx 命令基本语法如下:

redis 127.0.0.1:6379> SETNX KEY_NAME VALUE

可用版本:>= 1.0.0

返回值:设置成功,返回1, 设置失败,返回0

@RequestMapping("/stock_redis_lock")public String stock_redis_lock(){    //底层使用setnx命令
    Boolean aTrue = stringRedisTemplate.opsForValue().setIfAbsent(lock_key, "true");
    stringRedisTemplate.expire(lock_key,10, TimeUnit.SECONDS);//设置过期时间10秒
    if (!aTrue) {//设置失败则表示没有拿到分布式锁
        return "error";//这里可以给用户一个友好的提示    }    //获取当前库存
    int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(key));    if(stock>0){        int afterStock = stock-1;
        stringRedisTemplate.opsForValue().set(key,afterStock+"");
        System.out.println("扣减库存成功,剩余库存"+afterStock);
    }else {
        System.out.println("扣减库存失败");
    }
    stringRedisTemplate.delete(lock_key);//执行完毕释放分布式锁
    return "ok";
}

仍然设置库存数量为50,我们再用jmeter测试一下,把jmeter的测试地址改为127.0.0.1/stock_redis_lock,同样的设置再来测一次。

 使用Redis实现分布式锁的方法

测试了5次没有出现脏数据,把发送时间改为0,测了5次也没问题,然后又把线程数改为600,时间为0 ,循环4次,测了几次也是正常的。

上面实现分布式锁的代码已经是一个较为成熟的分布式锁的实现了,对大多数软件公司来说都已经满足需求了。但是上面代码还是有优化的空间,例如:

1)上面的代码我们是没有考虑异常情况的,实际情况下代码没有这么简单,可能还会有别的很多复杂的操作,都有可能会出现异常,所以我们释放锁的代码需要放在finally块里来保证即使是代码抛异常了释放锁的代码他依然会被执行。

2)还有,你有没有注意到,上面我们的分布式锁的代码的获取和设置过期时间的代码是两步操作第4行和第5行,即非原子操作,就有可能刚执行了第4行还没来得及执行第5行这台机器挂了,那么这个锁就没有设置超时时间,其他线程就一直无法获取,除非人工干预,所以这是一步优化的地方,Redis也提供了原子操作,那就是SET key value EX seconds  NX

科普时间

SET key value [EX seconds] [PX milliseconds] [NX|XX]  将字符串值 value 关联到 key

可选参数

从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:

  • EX second :设置键的过期时间为 second 秒。SET key value EX second 效果等同于 SETEX key second value

  • PX millisecond :设置键的过期时间为 millisecond 毫秒。SET key value PX millisecond 效果等同于 PSETEX key millisecond value

  • NX :只在键不存在时,才对键进行设置操作。SET key value NX 效果等同于 SETNX key value

  • XX :只在键已经存在时,才对键进行设置操作

SpringBoot的StringRedisTemplate也有对应的方法实现,如下代码: 

//假设库存编号是00001private String key = "stock:00001";private String lock_key = "lock_key:00001";
@Autowiredprivate StringRedisTemplate stringRedisTemplate;
@RequestMapping("/stock_redis_lock")public String stock_redis_lock() {
    String uuid = UUID.randomUUID().toString();    try {        //原子的设置key及超时时间
        Boolean aTrue = stringRedisTemplate.opsForValue().setIfAbsent(lock_key, "true", 30, TimeUnit.SECONDS);        if (!aTrue) {            return "error";
        }        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(key));        if (stock > 0) {            int afterStock = stock - 1;
            stringRedisTemplate.opsForValue().set(key, afterStock + "");
            System.out.println("扣减库存成功,剩余库存" + afterStock);
        } else {
            System.out.println("扣减库存失败");
        }
    } catch (NumberFormatException e) {
        e.printStackTrace();
    } finally {        //避免死锁
        if (uuid.equals(stringRedisTemplate.opsForValue().get(lock_key))) {
            stringRedisTemplate.delete(lock_key);
        }
    }    return "ok";
}

这样实现是否就完美了呢?嗯,对于并发量要求不高或者非大并发的场景的话这样实现已经可以了。但是对于抢购 ,秒杀这样的场景,当流量很大,这时候服务器网卡、磁盘IO、CPU负载都可能会达到极限,那么服务器对于一个请求的的响应时间势必变得比正常情况下慢很多,那么假设就刚才设置的锁的超时时间为10秒,如果某一个线程拿到锁之后因为某些原因没能在10秒内执行完毕锁就失效了,这时候其他线程就会抢占到分布式锁去执行业务逻辑,然后之前的线程执行完了,会去执行 finally 里的释放锁的代码就会把正在占有分布式锁的线程的锁给释放掉,实际上刚刚正在占有锁的线程还没执行完,那么其他线程就又有机会获得锁了...这样整个分布式锁就失效了,将会产生意想不到的后果。如下图模拟了这个场景。

使用Redis实现分布式锁的方法

所以这个问题总结一下,就是因为锁的过期时间设置的不合适或因为某些原因导致代码执行时间大于锁过期时间而导致并发问题以及锁被别的线程释放,以至于分布式锁混乱。在简单的说就是两个问题,1)自己的锁被别人释放 2)锁超时无法续时间。

第一个问题很好解决,在设置分布式锁时,我们在当前线程中生产一个唯一串将value设置为这个唯一值,然后在finally块里判断当前锁的value和自己设置的一样时再去执行delete,如下:

String uuid = UUID.randomUUID().toString();try {    //原子的设置key及超时时间,锁唯一值
    Boolean aTrue = stringRedisTemplate.opsForValue().setIfAbsent(lock_key,uuid,30,TimeUnit.SECONDS);    //...} finally {    //是自己设置的锁再执行delete
    if(uuid.equals(stringRedisTemplate.opsForValue().get(lock_key))){
        stringRedisTemplate.delete(lock_key);//避免死锁    }
}

问题一解决了(设想一下上述代码还有什么问题,一会儿讲),那锁的超时时间就很关键了,不能太大也不能太小,这就需要评估业务代码的执行时间,比如设置个10秒,20秒。即使是你的锁设置了合适的超时时间,也避免不了可能会发生上述分析的因为某些原因代码没在正常评估的时间内执行完毕,所以这时候的解决方案就是给锁续超时时间。大致思路就是,业务线程单独起一个分线程,定时去监听业务线程设置的分布式锁是否还存在,存在就说明业务线程还没执行完,那么就延长锁的超时时间,若锁已不存在则业务线程执行完毕,然后就结束自己。

“锁续命”的这套逻辑属实有点复杂啊,要考虑的问题太多了,稍不注意就会有bug。不要看上面实现分布式锁的代码没有几行,就认为实现起来很简单,如果说自己去实现的时候没有实际高并发的经验,肯定也会踩很多坑,例如,

1)锁的设置和过期时间的设置是非原子操作的,就可能会导致死锁。

2)还有上面遗留的一个,在finally块里判断锁是否是自己设置的,是的话再删除锁,这两步操作也不是原子的,假设刚判断完为true服务就挂了,那么删除锁的代码不会执行,就会造成死锁,即使是设置了过期时间,在没过期这段时间也会死锁。所以这里也是一个注意的点,要保证原子操作的话,Redis提供了执行Lua脚本的功能来保证操作的原子性,具体怎么使用不再展开。

所以,“锁续命”的这套逻辑实现起来还是有点复杂的,好在市面上已经有现成的开源框架帮我们实现了,那就是Redisson。

回到顶部

Redisson分布式锁的实现原理

使用Redis实现分布式锁的方法

实现原理:

1、首先Redisson会尝试进行加锁,加锁的原理也是使用类似Redis的setnx命令原子的加锁,加锁成功的话其内部会开启一个子线程

2、子线程主要负责监听,其实就是一个定时器,定时监听主线程是否还持有锁,持有则将锁的时间延时,否则结束线程

3、如果加锁失败则自旋不断尝试加锁

4、执行完代码主线程主动释放锁

那我们看一下使用后Redisson后的代码是什么样的。

1、首先在pom.xml文件添加Redisson的maven坐标

使用Redis实现分布式锁的方法


    org.redisson
    redisson
    3.12.5

使用Redis实现分布式锁的方法

2、我们要拿到Redisson的这个对象,如下配置Bean

使用Redis实现分布式锁的方法

@SpringBootApplicationpublic class RedisLockApplication {    public static void main(String[] args) {
        SpringApplication.run(RedisLockApplication.class, args);
    }
    @Bean    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379")
                .setDatabase(0);        return (Redisson) Redisson.create(config);
    }
}

使用Redis实现分布式锁的方法

3、然后我们获取Redisson的实例,使用其API进行加锁释放锁操作

使用Redis实现分布式锁的方法

//假设库存编号是00001private String key = "stock:00001";private String lock_key = "lock_key:00001";
@Autowiredprivate StringRedisTemplate stringRedisTemplate;/**
 * 使用Redisson实现分布式锁
 * @return
 */@RequestMapping("/stock_redisson_lock")public String stock_redisson_lock() {
    RLock redissonLock = redisson.getLock(lock_key);    try {
        redissonLock.lock();        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(key));        if (stock > 0) {            int afterStock = stock - 1;
            stringRedisTemplate.opsForValue().set(key, afterStock + "");
            System.out.println("扣减库存成功,剩余库存" + afterStock);
        } else {
            System.out.println("扣减库存失败");
        }
    } catch (NumberFormatException e) {
        e.printStackTrace();
    } finally {
        redissonLock.unlock();
    }    return "ok";
}

使用Redis实现分布式锁的方法

看这个Redisson的分布式锁提供的API是不是非常的简单?就像Java并发变成里AQS那套Lock机制一样,如下获取一把RedissonLock

RLock redissonLock = redisson.getLock(lock_key);

默认返回的是RedissonLock的对象,该对象实现了RLock接口,而RLock接口继承了JDK并发编程报包里的Lock接口

使用Redis实现分布式锁的方法

在使用Redisson加锁时,它也提供了很多API,如下

使用Redis实现分布式锁的方法

现在我们选择使用的是最简单的无参lock方法,简单的点进去跟一下看看他的源码,我们找到最终的执行加锁的代码如下:

使用Redis实现分布式锁的方法

我们可以看到其底层使用了Lua脚本来保证原子性,使用Redis的hash结构实现的加锁,以及可重入锁。

比我们自己实现分布式锁看起来还要简单,但是我们自己写的锁功能他都有,我们没有的他也有。比如,他实现的分布式锁是支持可重入的,也支持可等待,即尝试等待一定时间,没拿到锁就返回false。上述代码中的redissonLock.lock();是一直等待,内部自旋尝试加锁。

Distributed Java locks and synchronizers 

Lock 

FairLock 

MultiLock 

RedLock 

ReadWriteLock 

Semaphore 

PermitExpirableSemaphore 

CountDownLatch

redisson.org

Redisson提供了丰富的API,内部运用了大量的Lua脚本保证原子操作,篇幅原因redisson实现锁的代码暂不分析了。

注意:在上述示例代码中,为了方便演示,查询redis库存、修改库存并非原子操作,实际这两部操作也得保证原子行,可以用redis自带的Lua脚本功能去实现

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发
相关文章 更多
精品专题 更多
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

Mac软件 更多
Shapr3D macOS版
Shapr3D macOS版

Shapr3D是一款面向工业设计、机械工程、建筑概念和三维打印工作流的CAD软件。Mac版采用Parasolid建模内核,支持草图约束、实体建模、工程图、可视化渲染及常见CAD格式交换,并可通过账户在多台设备之间同步项目。

REAPER macOS版
REAPER macOS版

REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

Ableton Live macOS版
Ableton Live macOS版

Ableton Live 是面向音乐制作人与现场表演者的数字音频工作站,提供编曲视图、独具特色的现场视图、音频录制、MIDI创作、实时变速、乐器及效果器。Mac版原生支持Apple芯片,并可连接音频接口、MIDI控制器和第三方插件。

Adobe After Effects macOS版
Adobe After Effects macOS版

Adobe After Effects 是面向动态图形、影视特效与视频合成的专业创作软件。它提供关键帧动画、遮罩抠像、运动跟踪、三维空间、文字动画和表达式等工具,并可与 Premiere Pro、Photoshop、Illustrator

Adobe Audition macOS版
Adobe Audition macOS版

Adobe Audition 是面向播客、影视后期、音乐制作及内容创作者的专业音频工作站,提供波形精修、多轨录制与混音、频谱编辑、降噪修复、响度匹配和效果处理工具,并可与 Adobe Premiere 协同完成视频声音制作。

Adobe Bridge macOS版
Adobe Bridge macOS版

Adobe Bridge 是面向摄影师、设计师及内容团队的免费数字资产管理工具,可集中预览照片、视频和 Adobe 项目文件,并利用评级、标签、关键词、元数据、筛选与收藏集完成分类检索。它还支持批量重命名、格式导出、相机导入及 Camera

Adobe Illustrator macOS版
Adobe Illustrator macOS版

Adobe Illustrator 是 Adobe 推出的专业矢量图形设计软件,适合在 Mac 上制作标志、图标、插画、包装、信息图及印刷版面。它以路径和锚点构建可无损缩放的图形,并提供钢笔、形状、文字、图像描摹、渐变、画板及生成式功能。

Adobe InDesign macOS版
Adobe InDesign macOS版

Adobe InDesign 是面向印刷品与数字出版物的专业版面设计软件,可在 Mac 上制作书籍、杂志、宣传册、海报、报告及交互式文档。它提供精细的文字样式、网格、母版、长文档管理和印前输出能力。

Adobe Lightroom Classic macOS版
Adobe Lightroom Classic macOS版

Adobe Lightroom Classic 是面向桌面摄影工作流的照片管理与后期处理软件,可将原始照片保存在本地硬盘,通过目录、关键词、评分和收藏夹高效整理图库,并提供 RAW 冲印、蒙版、镜头校正、批量同步及多格式导出等功能。

Adobe Media Encoder macOS版
Adobe Media Encoder macOS版

Adobe Media Encoder 是 Adobe 推出的专业音视频编码与转码工具,可通过队列、预设和监视文件夹批量完成格式转换、代理文件创建及多平台成片输出,并与 Premiere Pro、After Effects 等软件紧密协作。

Adobe Photoshop macOS版
Adobe Photoshop macOS版

Adobe Photoshop 是面向摄影师、设计师和内容创作者的专业图像处理软件,提供图层、蒙版、选区、修复、调色、文字排版、智能对象及生成式编辑等能力。Mac 版支持 Intel 与 Apple Silicon 处理器。

Adobe Premiere Pro macOS版
Adobe Premiere Pro macOS版

Adobe Premiere Pro 是面向专业视频制作的非线性剪辑软件,提供多轨时间线、调色、音频处理、字幕、代理工作流和多格式输出能力,并可与 After Effects、Audition、Photoshop 及 Frame.io 协同

WINDOWS 更多
3dmax(3ds max)
3dmax(3ds max)

Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

photoshop
photoshop

Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

Blender
Blender

Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Mac
Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

即将离开本站
您即将前往第三方网站,请确认是否继续?