1、分布式锁实现原理与最佳实践(一)

单体的应用开发场景中涉及并发同步时,大家往往采用Synchronized(同步)或同一个JVM内Lock机制来解决多线程间的同步问题。而在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题,这种跨机器的锁就是分布式锁。接下来本文将为大家分享分布式锁的最佳实践。

一、超卖问题复现

1.1 现象

存在如下的几张表:
商品表
在这里插入图片描述
订单表
在这里插入图片描述
订单item表
在这里插入图片描述
商品的库存为1,但是并发高的时候有多笔订单。

错误案例一:数据库update相互覆盖
直接在内存中判断是否有库存,计算扣减之后的值更新数据库,并发的情况下会导致相互覆盖发生:

@Transactional(rollbackFor = Exception.class)
public Long createOrder() throws Exception {Product product = productMapper.selectByPrimaryKey(purchaseProductId);// ... 忽略校验逻辑//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}// 计算剩余库存Integer leftCount = currentCount - purchaseProductNum;// 更新库存product.setCount(leftCount);product.setGmtModified(new Date());productMapper.updateByPrimaryKeySelective(product);Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();
}

错误案例二:扣减串行执行,但是库存被扣减为负数

在 SQL 中加入运算避免值的相互覆盖,但是库存的数量变为负数,因为校验库存是否足够还是在内存中执行的,并发情况下都会读到有库存:


@Transactional(rollbackFor = Exception.class)
public Long createOrder() throws Exception {Product product = productMapper.selectByPrimaryKey(purchaseProductId);// ... 忽略校验逻辑//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}// 使用 set count =  count - #{purchaseProductNum,jdbcType=INTEGER}, 更新库存productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();
}

错误案例三:使用 synchronized 实现内存中串行校验,但是依旧扣减为负数
因为我们使用的是事务的注解,synchronized加在方法上,方法执行结束的时候锁就会释放,此时的事务还没有提交,另一个线程拿到这把锁之后就会有一次扣减,导致负数。

@Transactional(rollbackFor = Exception.class)
public synchronized Long createOrder() throws Exception {Product product = productMapper.selectByPrimaryKey(purchaseProductId);// ... 忽略校验逻辑//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}// 使用 set count =  count - #{purchaseProductNum,jdbcType=INTEGER}, 更新库存productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();
}

1.2 解决办法

从上面造成问题的原因来看,只要是扣减库存的动作,不是原子性的。多个线程同时操作就会有问题。
单体应用:使用本地锁 + 数据库中的行锁解决分布式应用:使用数据库中的乐观锁,加一个 version 字段,利用CAS来实现,会导致大量的 update 失败使用数据库维护一张锁的表 + 悲观锁 select,使用 select for update 实现使用Redis 的 setNX实现分布式锁使用zookeeper的watcher + 有序临时节点来实现可阻塞的分布式锁使用Redisson框架内的分布式锁来实现使用curator 框架内的分布式锁来实现

二、单体应用解决超卖的问题

正确示例:将事务包含在锁的控制范围内

保证在锁释放之前,事务已经提交。
//@Transactional(rollbackFor = Exception.class)
public synchronized Long createOrder() throws Exception {TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);Product product = productMapper.selectByPrimaryKey(purchaseProductId);if (product == null) {platformTransactionManager.rollback(transaction1);throw new Exception("购买商品:" + purchaseProductId + "不存在");}//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {platformTransactionManager.rollback(transaction1);throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}productMapper.updateProductCount(purchaseProductNum, new Date(), product.getId());Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();platformTransactionManager.commit(transaction1);
}

正确示例:使用synchronized的代码块

public Long createOrder() throws Exception {Product product = null;//synchronized (this) {//synchronized (object) {synchronized (DBOrderService2.class) {TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);product = productMapper.selectByPrimaryKey(purchaseProductId);if (product == null) {platformTransactionManager.rollback(transaction1);throw new Exception("购买商品:" + purchaseProductId + "不存在");}//商品当前库存Integer currentCount = product.getCount();System.out.println(Thread.currentThread().getName() + "库存数:" + currentCount);//校验库存if (purchaseProductNum > currentCount) {platformTransactionManager.rollback(transaction1);throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}productMapper.updateProductCount(purchaseProductNum, new Date(), product.getId());platformTransactionManager.commit(transaction1);}TransactionStatus transaction2 = platformTransactionManager.getTransaction(transactionDefinition);Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();// ... 省略 SetorderItemMapper.insertSelective(orderItem);platformTransactionManager.commit(transaction2);return order.getId();

正确示例:使用Lock

private Lock lock = new ReentrantLock();public Long createOrder() throws Exception{  Product product = null;lock.lock();TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);try {product = productMapper.selectByPrimaryKey(purchaseProductId);if (product==null){throw new Exception("购买商品:"+purchaseProductId+"不存在");}//商品当前库存Integer currentCount = product.getCount();System.out.println(Thread.currentThread().getName()+"库存数:"+currentCount);//校验库存if (purchaseProductNum > currentCount){throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件,无法购买");}productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());platformTransactionManager.commit(transaction1);} catch (Exception e) {platformTransactionManager.rollback(transaction1);} finally {// 注意抛异常的时候锁释放不掉,分布式锁也一样,都要在这里删掉lock.unlock();}TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();// ... 省略 SetorderItemMapper.insertSelective(orderItem);platformTransactionManager.commit(transaction);return order.getId();
}

三、常见分布式锁的使用

上面使用的方法只能解决单体项目,当部署多台机器的时候就会失效,因为锁本身就是单机的锁,所以需要使用分布式锁来实现。
3.1 数据库乐观锁

数据库中的乐观锁,加一个version字段,利用CAS来实现,乐观锁的方式支持多台机器并发安全。但是并发量大的时候会导致大量的update失败
3.2 数据库分布式锁

db操作性能较差,并且有锁表的风险,一般不考虑。
3.2.1 简单的数据库锁
在这里插入图片描述
select for update
直接在数据库新建一张表:
在这里插入图片描述
锁的code预先写到数据库中,抢锁的时候,使用select for update查询锁对应的key,也就是这里的code,阻塞就说明别人在使用锁。

// 加上事务就是为了 for update 的锁可以一直生效到事务执行结束
// 默认回滚的是 RunTimeException
@Transactional(rollbackFor = Exception.class)
public String singleLock() throws Exception {log.info("我进入了方法!");DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo");if (distributeLock==null) {throw new Exception("分布式锁找不到");}log.info("我进入了锁!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return "我已经执行完成!";
}<select id="selectDistributeLock" resultType="com.deltaqin.distribute.model.DistributeLock">select * from distribute_lockwhere businessCode = #{businessCode,jdbcType=VARCHAR}for update
</select>

使用唯一键作为限制,插入一条数据,其他待执行的SQL就会失败,当数据删除之后再去获取锁 ,这是利用了唯一索引的排他性。
insert lock
直接维护一张锁表:

@Autowired
private MethodlockMapper methodlockMapper;@Override
public boolean tryLock() {try {//插入一条数据   insert intomethodlockMapper.insert(new Methodlock("lock"));}catch (Exception e){//插入失败return false;}return true;
}@Override
public void waitLock() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
}@Override
public void unlock() {//删除数据   deletemethodlockMapper.deleteByMethodlock("lock");System.out.println("-------释放锁------");

3.3 Redis setNx

Redis 原生支持的,保证只有一个会话可以设置成功,因为Redis自己就是单线程串行执行的。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.redis.host=localhost

封装一个锁对象:

@Slf4j
public class RedisLock implements AutoCloseable {private RedisTemplate redisTemplate;private String key;private String value;//单位:秒private int expireTime;/*** 没有传递 value,因为直接使用的是随机值*/public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){this.redisTemplate = redisTemplate;this.key = key;this.expireTime=expireTime;this.value = UUID.randomUUID().toString();}/*** JDK 1.7 之后的自动关闭的功能*/@Overridepublic void close() throws Exception {unLock();}/*** 获取分布式锁* SET resource_name my_random_value NX PX 30000* 每一个线程对应的随机值 my_random_value 不一样,用于释放锁的时候校验* NX 表示 key 不存在的时候成功,key 存在的时候设置不成功,Redis 自己是单线程,串行执行的,第一个执行的才可以设置成功* PX 表示过期时间,没有设置的话,忘记删除,就会永远不过期*/public boolean getLock(){RedisCallback<Boolean> redisCallback = connection -> {//设置NXRedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();//设置过期时间Expiration expiration = Expiration.seconds(expireTime);//序列化keybyte[] redisKey = redisTemplate.getKeySerializer().serialize(key);//序列化valuebyte[] redisValue = redisTemplate.getValueSerializer().serialize(value);//执行setnx操作Boolean result = connection.set(redisKey, redisValue, expiration, setOption);return result;};//获取分布式锁Boolean lock = (Boolean)redisTemplate.execute(redisCallback);return lock;}/*** 释放锁的时候随机数相同的时候才可以释放,避免释放了别人设置的锁(自己的已经过期了所以别人才可以设置成功)* 释放的时候采用 LUA 脚本,因为 delete 没有原生支持删除的时候校验值,证明是当前线程设置进去的值* 脚本是在官方文档里面有的*/public boolean unLock() {// key 是自己才可以释放,不是就不能释放别人的锁String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);List<String> keys = Arrays.asList(key);// 执行脚本的时候传递的 value 就是对应的值Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);log.info("释放锁的结果:"+result);return result;}
}每次获取的时候,自己线程需要new对应的RedisLockpublic String redisLock(){log.info("我进入了方法!");try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){if (redisLock.getLock()) {log.info("我进入了锁!!");Thread.sleep(15000);}} catch (InterruptedException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}log.info("方法执行完成");return "方法执行完成";
}

3.4 zookeeper 瞬时znode节点 + watcher监听机制

临时节点具备数据自动删除的功能。当client与ZooKeeper连接和session断掉时,相应的临时节点就会被删除。zk有瞬时和持久节点,瞬时节点不可以有子节点。会话结束之后瞬时节点就会消失,基于zk的瞬时有序节点实现分布式锁:
多线程并发创建瞬时节点的时候,得到有序的序列,序号最小的线程可以获得锁;

其他的线程监听自己序号的前一个序号。前一个线程执行结束之后删除自己序号的节点;

下一个序号的线程得到通知,继续执行;

以此类推,创建节点的时候,就确认了线程执行的顺序。
在这里插入图片描述

<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.14</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions>
</dependency>

zk 的观察器只可以监控一次,数据发生变化之后可以发送给客户端,之后需要再次设置监控。exists、create、getChildren三个方法都可以添加watcher ,也就是在调用方法的时候传递true就是添加监听。注意这里Lock 实现了Watcher和AutoCloseable:

当前线程创建的节点是第一个节点就获得锁,否则就监听自己的前一个节点的事件:

/*** 自己本身就是一个 watcher,可以得到通知* AutoCloseable 实现自动关闭,资源不使用的时候*/
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {private ZooKeeper zooKeeper;/*** 记录当前锁的名字*/private String znode;public ZkLock() throws IOException {this.zooKeeper = new ZooKeeper("localhost:2181",10000,this);}public boolean getLock(String businessCode) {try {//创建业务 根节点Stat stat = zooKeeper.exists("/" + businessCode, false);if (stat==null){zooKeeper.create("/" + businessCode,businessCode.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);}//创建瞬时有序节点  /order/order_00000001znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);//获取业务节点下 所有的子节点List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);//获取序号最小的(第一个)子节点Collections.sort(childrenNodes);String firstNode = childrenNodes.get(0);//如果创建的节点是第一个子节点,则获得锁if (znode.endsWith(firstNode)){return true;}//如果不是第一个子节点,则监听前一个节点String lastNode = firstNode;for (String node:childrenNodes){if (znode.endsWith(node)){zooKeeper.exists("/"+businessCode+"/"+lastNode,true);break;}else {lastNode = node;}}synchronized (this){wait();}return true;} catch (Exception e) {e.printStackTrace();}return false;}@Overridepublic void close() throws Exception {zooKeeper.delete(znode,-1);zooKeeper.close();log.info("我已经释放了锁!");}@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted){synchronized (this){notify();}}}
}

3.5 zookeeper curator

在实际的开发中,不建议去自己“重复造轮子”,而建议直接使用Curator客户端中的各种官方实现的分布式锁,例如其中的InterProcessMutex可重入锁。

<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.2.0</version><exclusions><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions>
</dependency>
@Bean(initMethod="start",destroyMethod = "close")
public CuratorFramework getCuratorFramework() {RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);return client;
}

框架已经实现了分布式锁。zk的Java客户端升级版。使用的时候直接指定重试的策略就可以。

官网中分布式锁的实现是在curator-recipes依赖中,不要引用错了。

@Autowired
private CuratorFramework client;@Test
public void testCuratorLock(){InterProcessMutex lock = new InterProcessMutex(client, "/order");try {if ( lock.acquire(30, TimeUnit.SECONDS) ) {try  {log.info("我获得了锁!!!");}finally  {lock.release();}}} catch (Exception e) {e.printStackTrace();}client.close();
}

3.6 Redission

重新实现了Java并发包下处理并发的类,让其可以跨JVM使用,例如CHM等。
3.6.1 非SpringBoot项目引入
https://redisson.org/
引入Redisson的依赖,然后配置对应的XML即可:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.11.2</version><exclusions><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions>
</dependency>

编写相应的redisson.xml

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:redisson="http://redisson.org/schema/redisson"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://redisson.org/schema/redissonhttp://redisson.org/schema/redisson/redisson.xsd
"><redisson:client><redisson:single-server address="redis://127.0.0.1:6379"/></redisson:client>
</beans>

配置对应@ImportResource(“classpath*:redisson.xml”)资源文件。

3.6.2 SpringBoot项目引入
或者直接使用springBoot的starter即可。
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.19.1</version>
</dependency>

修改application.properties即可:#spring.redis.host=
3.6.3 设置配置类

@Bean
public RedissonClient getRedissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");return Redisson.create(config);
}

3.6.4 使用

@Test
public void testRedissonLock() {RLock rLock = redisson.getLock("order");try {rLock.lock(30, TimeUnit.SECONDS);log.info("我获得了锁!!!");Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}finally {log.info("我释放了锁!!");rLock.unlock();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/172147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安卓系统修图软件(二)

晚上好&#xff0c;自上一次博主分享修图软件之后&#xff0c;今天博主将带来第二期安卓修图软件的推送&#xff0c;个个都是宝藏&#xff0c;建议大家赶紧体验哦。 1.canva可画 如果说有一款手机APP可以与PS媲美&#xff0c;那么一定非canvas莫属。这款强大的修图软件支持海报…

java springboot测试类虚拟MVC环境 匹配请求头指定key与预期值是否相同

上文 java springboot测试类虚拟MVC环境 匹配返回值与预期内容是否相同 (JSON数据格式) 版 中 我们展示 json匹配内容的方式 那么 本文我们来看看Content-Type属性的匹配方式 首先 我们从返回体可以看出 Content-Type 在请求头信息 Headers 中 我们直接将测试类代码更改如下 …

企业文档文件管理软件推荐:提升管理效率与数据安全性

Zoho WorkDrive企业网盘是一种高效的文件管理工具&#xff0c;它不仅可以为组织搭建统一、高效、安全、智能的内容管理体系&#xff0c;还能够提供大规模支撑、海量数据处理、非结构化数据治理、智能挖掘与洞察等服务能力。通过这些服务&#xff0c;企业可以更好地管理和利用其…

leetcode 41. 缺失的第一个正数

目录 暴力排序 桶排序 桶排序Set 桶排序分治思想 官方题解 桶排序数组内标记 桶排序额外数组标记&#xff08;更好理解&#xff09; 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额…

Rust在Web开发中的应用

欢迎关注我的公众号lincyang新自媒体&#xff0c;回复关键字【程序员经典书单】&#xff0c;领取程序员的100本经典书单 大家好&#xff01;我是lincyang。 今天我们将一起深入探索Rust在Web开发领域的应用。尽管Rust最初设计用于系统编程&#xff0c;但其性能、安全性和现代并…

只狼 资源分享

版本介绍 v1.06版|容量15GB|官方简体中文|支持键盘.鼠标.手柄|赠官方原声4首BGM|赠多项修改器|赠一周目全义手忍具强化通关存档|2020年01月15号更新 只狼中文设置&#xff1a; https://jingyan.baidu.com/article/cb5d6105bc8556005d2fe048.html 只狼键盘对应按键&#xff1…

windows本地dockr的clickhouse链接本地mysql服务,连接不上

不想看过成的&#xff0c;解决办法在最后面 报错信息&#xff1a; SQL 错误 [1000] [08000]: Poco::Exception. Code: 1000, e.code() 0, Exception: Connections to all replicas failed: test1localhost:3306 as user root (version 21.12.3.32 (official build)) , serve…

C# WPF上位机开发(掌握一点c#基础)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 wpf虽然比较简单&#xff0c;但是最好还是要有一点c#的基础比较好。本身wpf有点类似于web开发&#xff0c;前端和html差不多&#xff0c;后端则和j…

SpringBoot事务处理

一、事务回顾 回顾地址&#xff1a; 深入理解数据库事务&#xff08;超详细&#xff09;_数据库事务操作_Maiko Star的博客-CSDN博客 事务&#xff1a; 是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 事…

隐写-MISC-bugku-解题步骤

——CTF解题专栏—— 题目信息&#xff1a; 题目&#xff1a;隐写 作者&#xff1a;CyberFl0wer 提示&#xff1a;无 解题附件&#xff1a; 解题思路&#xff1a; 这张图片一看&#xff01;哦呦~背景还是透明的&#xff0c;那我肯定要尝试给他换换色&#xff08;不可以色色.jpg…

重生之我是一名程序员 42——字符分类函数

哈喽啊大家晚上好&#xff01;今天呢给大家带来一些超简单的知识&#xff0c;大家是需要浅浅理解就行了。所以今天给大家带来的知识是——字符分类函数。 首先呢还是给大家介绍一下它们&#xff0c;字符分类函数是一种函数&#xff0c;它根据一定的规则将字符分组或分类。在编…

Map和Set小总结【温习】

目录 一、概念与模型 二、Map的使用 三、Set的说明 一些小练习 四、哈希表 1.概念 2.冲突 2.1、概念 2.2、冲突-->避免 2.3、冲突-->解决 &#xff08;1&#xff09;闭散列 &#xff08;2&#xff09;开散列 2.4、其他问题 一、概念与模型 1.概念&#xff1a…

三菱GX WORRKS3 下载与安装

目录 下载 安装 准备好安装包 对电脑系统要求 安装 因为小编公司需要&#xff0c;所以开始了三菱plc软件的学习&#xff0c;并从今天开始记录学习&#xff0c;希望小编的内容能帮到你&#xff0c;对你的学习有帮助&#xff01; 下载 三菱电机官网 当然了&#xff0c;需要…

【C++】类和对象(下篇)

这里是目录 构造函数&#xff08;续&#xff09;构造函数体赋值初始化列表 explicit关键字隐式类型转换 static成员友元友元函数友元类 内部类匿名对象匿名对象的作用const引用匿名对象 构造函数&#xff08;续&#xff09; 构造函数体赋值 在创建对象时&#xff0c;编译器通…

五种多目标优化算法(MOGWO、MOLPB、MOJS、NSGA3、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标灰狼优化算法MOGWO 多目标应用&#xff1a;基于多目标灰狼优化算法MOGWO求解微电网多目标优化调度&#xff08;MATLAB代码&#xff09;-CSDN博客 &#xff08;2&#xff09;多目标学习者行为优化算法MOLPB 多目标学习…

佳易王幼儿园缴费系统软件编程应用实例

佳易王幼儿园缴费系统软件编程实例 佳易王幼儿园缴费系统功能&#xff1a; 1、系统设置 2、班级设置 3、其他费用名称 4、学生信息管理 5、学生缴费 6、统计报表 7、备份全部数据 软件试用版下载可以点击下方官网卡片

由走“贸工技”的联想联想到传统OEM,带给了自己那些思考?

2022年1月16日&#xff0c;自己来到魔都的第1597天&#xff0c;这城市还是保持着相似的容颜&#xff0c;而自己却悄悄的起了变化。 以前对时间概念其实不是特别敏感&#xff0c;感觉自己有大把的时光可以浪费&#xff08;虽然知道死亡是个永远无法逃避的话题&#xff09;&#…

MATLAB 和 Simulink 官方文档下载地址

MATLAB 官方文档中文版下载网址&#xff1a; https://ww2.mathworks.cn/help/pdf_doc/matlab/index.html 如图&#xff1a; MATLAB 官方文档英文版下载网址&#xff1a; https://ww2.mathworks.cn/help/pdf_doc/matlab/index.html?langen 如图&#xff1a; Simulink 官…

Vue - Vue配置proxy代理,开发、测试、生产环境

1、新建三个环境的配置文件 在src同级目录也就是根目录下新建文件&#xff1a;.env.development&#xff08;开发环境&#xff09;、.env.test&#xff08;测试环境&#xff09;、.env.production文件&#xff08;生产环境&#xff09; 2、三个环境的配置文件 开发环境 .env…

重量级消息,微软将ThreadX RTOS全家桶贡献给Eclipse基金会,免费供大家商用,宽松的MIT授权方式

从明年第1季度开始&#xff0c;任何人&#xff0c;任何厂家的芯片都可以免费商用&#xff0c;MIT授权就这点好。 贡献出来后&#xff0c;多方可以一起努力开发&#xff0c;当前首批兴趣小组AMD, Cypherbridge, Microsoft, NXP, PX5, Renesas, ST Microelectronics, Silicon Lab…