分布式锁【分布式锁概述、业务介绍、创建SpringBoot项目】(一)-全面详解(学习总结---从入门到深化)

news2024/11/24 7:08:56

目录

分布式锁概述

分布式锁问题_业务介绍

分布式锁问题_创建SpringBoot项目


分布式锁概述

 为什么需要分布式锁

 在单机部署的系统中,使用线程锁来解决高并发的问题,多线程访问共享变量的问题达到数据一致性,如使用synchornized、 ReentrantLock等。

 

 但是在后端集群部署的系统中,程序在不同的JVM虚拟机中运行, 且因为synchronized或ReentrantLock都只能保证同一个JVM进程 中保证有效,所以这时就需要使用分布式锁了。

什么是分布式锁

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的 一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享 了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

 分布式锁的特点

分布式锁问题_业务介绍

案列介绍

 技术选型

 创建表

创建订单表

CREATE TABLE `t_order`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL,
  `order_status` int(1) NULL DEFAULT NULL
COMMENT '订单状态 1 待支付 2已支付',
  `receiver_name` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '收货人名字',
  `receiver_mobile` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '收货人手机',
  `order_amount` decimal(10, 2) NULL DEFAULT
NULL COMMENT '订单价格',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;

创建商品表

CREATE TABLE `product`  (
  `id` int(11) NOT NULL,
  `product_name` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '商品名字',
  `price` decimal(10, 2) NULL DEFAULT NULL
COMMENT '商品价格',
  `count` bigint(50) UNSIGNED NULL DEFAULT NULL
COMMENT '库存',
  `product_desc` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '商品描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES (1001,'拯救者',100.00, 5,'好用实惠', 1);

创建订单商品关联表

CREATE TABLE `order_item`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL,
  `order_id` varchar(36) CHARACTER SET utf8
COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '订单ID',
  `produce_id` int(11) NULL DEFAULT NULL
COMMENT '商品ID',
  `purchase_price` decimal(10, 2) NULL DEFAULT
NULL COMMENT '购买价格',
  `purchase_num` int(11) NULL DEFAULT NULL
COMMENT '购买数量',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;

分布式锁问题_创建SpringBoot项目

 引入依赖

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>

修改配置文件

spring:
 application:
   name: lock
 datasource:
   url: jdbc:mysql://192.168.66.100:3306/distribute?serverTimezone=UTC
   username: root
   password01: 123456
   driver-class-name: com.mysql.cj.jdbc.Driver
server:
 port: 9091

编写主启动类

@Slf4j
@MapperScan("com.tong.lock.mapper")
@SpringBootApplication
public class LockdemoApplication {
    public static void main(String[] args) {
       SpringApplication.run(LockdemoApplication.class, args);
        log.info("************** 分布式锁 **************");
   }
}

代码生成

使用Mybaits Plus生成订单表、商品表、订单商品关联表的相关代码。

package com.tong.lock.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
import java.util.List;
public class CodeGenerator {
 public static void main(String[] args) {
      FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/distribute", "root", "123456")
               .globalConfig(builder -> {
                    builder.author("itbaizhan")// 设置作者
                           .commentDate("MMdd") // 注释日期格式
                           .outputDir(System.getProperty("user.dir")+ "/src/main/java/") // 指定输出目录
                           .fileOverride(); //覆盖文件
               })
                // 包配置
               .packageConfig(builder -> {
                  builder.parent("com.itbaizhan.lock") // 包名前缀
                           .entity("entity")//实体类包名
                           .mapper("mapper")//mapper接口包名
                           .service("service"); //service包名
               })
               .strategyConfig(builder -> {
                    List<String> strings = Arrays.asList("t_order");
                    // 设置需要生成的表名
                    builder.addInclude(strings)
                            // 开始实体类配置
                           .entityBuilder()
                            // 开启lombok模型
                           .enableLombok()
                            //表名下划线转驼峰
                           .naming(NamingStrategy.underline_to_camel)
                            //列名下划线转驼峰
                           .columnNaming(NamingStrategy.underline_to_camel);
               })
               .execute();
   }
}

编写创建订单接口

public interface ITOrderService extends IService<TOrder> {
    /**
     * 创建订单
     * @return
     */
    String createOrder(Integer productId,Integer count);
}

实现创建订单接口

package com.tong.lock.service.impl;
import com.tong.lock.entity.OrderItem;
import com.tong.lock.entity.Product;
import com.tong.lock.entity.TOrder;
import com.tong.lock.mapper.OrderItemMapper;
import com.tong.lock.mapper.ProductMapper;
import com.tong.lock.mapper.TOrderMapper;
import com.tong.lock.service.ITOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantLock;
/**
* <p>
* 服务实现类
* </p>
*
* @author tong
* @since 05-25
*/
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
    @Resource
    OrderItemMapper orderItemMapper;
    @Resource
    ProductMapper productMapper;    
    /**
     * 创建订单
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public  String createOrder(Integer productId,Integer count) {
  // 1、根据商品id查询商品信息
            Product product = productMapper.selectById(productId);
            // 2、判断商品是否存在
            if (product == null){
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
           }
            // 3、校验库存
            if( count > product.getCount() ){
                throw   new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
           }
            // 4、计算库存
            Integer leftCount = product.getCount() - count;
            // 5、更新库存
            product.setCount(leftCount);
            productMapper.updateById(product);
            // 6、 创建订单
            TOrder order = new TOrder();
            order.setOrderStatus(1);//待处理
            order.setReceiverName("张三");
            order.setReceiverMobile("18587781068");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
            baseMapper.insert(order);
            // 7、 创建订单和商品关系数据
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchasePrice(product.getPrice());
            orderItem.setPurchaseNum(count);
            orderItemMapper.insert(orderItem);
            return order.getId();
   }
}

编写创建订单api接口

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private ITOrderService iOrderService;
    /**
     * 创建订单
     * @param productId 商品id
     * @param count 商品数量
     * @return
     */
    @PostMapping("/create")
    public String createOrder(Integer productId,Integer count){
      return iOrderService.createOrder(productId,count);
   }
}

测试订单

复习:

 

 Single Thread Execution 设计模式

机场过安检

Single Thread Execution 模式是指在同一时刻只能有一个线程去访问共享资源,就 像独木桥一样每次只允许一人通行,简单来说, Single Thread Execution 就是采用排 他式的操作保证在同一时刻只能有一个线程访问共享资源。 相信大家都有乘坐飞机的经历,在进入登机口之前必须经过安全检査,安检口类似于独木桥,每次只能通过一个人,工作人员除了检査你的登机牌以外,还要联网检查身份证信息以及是否携带危险物品,如下图所示。

 

 非线程安全

先模拟一个非线程安全的安检口类,旅客(线程)分别手持登机牌和身份证接受工作人 员的检查,示例代码如下所示。

package com.tong.chapter14;
public class FlightSecurity {
    private int count = 0;
    private String boardingPass = "null";// 登机牌
    private String idCard = "null";// 身份证
    public void pass(String boardingPass, String idCard) {
             this.boardingPass = boardingPass;
             this.idCard = idCard;
             this.count++;
             check();
}
    private void check() {
    // 简单的业务,当登机牌和身份证首位不相同时则表示检查不通过
    if (boardingPass.charAt(0) != idCard.charAt(0)) {
          throw new RuntimeException("-----Exception-----" + toString());
   }
}
@Override
public String toString() {
           return "FlightSecurity{" + "count=" + count + ", boardingPass='" + boardingPass + '\'' + ", idCard='" + idCard + '\'' + '}';
    }
}

FlightSecurity 比较简单,提供了一个 pass 方法,将旅客的登机牌和身份证传递给 pass 方法,在 pass 方法中调用 check 方法对旅客进行检查,检查的逻辑也足够的简单, 只需要检测登机牌和身份证首位是否相等(当然这样在现实中非常不合理,但是为了使测试简单我们约定这么做),我们看以下代码所示的测试。

package com.tong.chapter14;
public class FlightSecurityTest {
       static class Passengers extends Thread {
       // 机场安检类
       private final FlightSecurity flightSecurity;
       // 旅客身份证
       private final String idCard;
       // 旅客登机牌
       private final String boardingPass;
       public Passengers(FlightSecurity flightSecurity, String idCard, String boardingPass) {
       this.flightSecurity = flightSecurity;
       this.idCard = idCard;
       this.boardingPass = boardingPass;
}
@Override
public void run() {
   while (true) {
         // 旅客不断地过安检
         flightSecurity.pass(boardingPass, idCard);
       }
    }
}
public static void main(String[] args) {
      // 定义三个旅客,身份证和登机牌首位均相同
       final FlightSecurity flightsecurity = new FlightSecurity();
       new Passengers(flightsecurity, "Al23456", "AF123456").start();
       new Passengers(flightsecurity, "B123456", "BF123456").start();
       new Passengers(flightsecurity, "C123456", "CF123456").start();
    }
}

看起来每一个客户都是合法的,因为每一个客户的身份证和登机牌首字母都一样,运行 上面的程序却出现了错误,而且错误的情况还不太一样,运行多次,发现了两种类型的错误信息,程序输出如下:

java.lang.RuntimeException: -----Exception-----FlightSecurity{count=218,boardingPass='AF123456', idCard='B123456'}
java.lang.RuntimeException: -----Exception-----FlightSecurity{count=676,boardingPass='BF123456', 

首字母相同检查不能通过和首字母不相同检查不能通过,为什么会出现这样的情况呢? 首字母相同却不能通过?更加奇怪的是传入的参数明明全都是首字母相同的,为什么会出现首字母不相同的错误呢。

 问题分析

首字母相同却未通过检查

1)线程 A 调用 pass 方法,传人”A123456”“AF123456”并且对 idcard 赋值成功,由 于 CPU 调度器时间片的轮转,CPU 的执行权归 B 线程所有。

2) 线程 B 调用 pass 方法,传入”B123456”“BF123456”并且对 idcard 赋值成功, 覆盖 A 线程赋值的 idCard。

3)线程 A 重新获得 CPU 的执行权,将 boardingPass 赋于 AF123456,因此 check 无 法通过。

4)在输出 toString 之前,B 线程成功将 boardingPass 覆盖为 BF123456。

为何出现首字母不相同的情况 

1)线程 A 调用 pass 方法,传入”A123456”“AF123456”并且对 id Card 赋值成功,由 于 CPU 调度器时间片的轮转,CPU 的执行权归 B 线程所有。

2)线程 B 调用 pass 方法,传入”B123456”“BF123456”并且对 id Card 赋值成功,覆 盖 A 线程赋值的 idCard。

3)线程 A 重新获得 CPU 的执行权,将 boardingPass 赋于 AF123456,因此 check 无 法通过。

4)线程 A 检查不通过,输出 idcard=”A123456”和 boardingPass=”BF123456”。

 线程安全

上面出现的问题说到底就是数据同步的问题,虽然线程传递给 pass 方法的两个参数能 够百分之百地保证首字母相同,可是在为 FlightSecurity 中的属性赋值的时候会出现多个线程交错的情况,结合我们之前所讲内容可知,需要对共享资源增加同步保护,改进代码如下。

public synchronized void pass(String boardingPass, String idCard) {
       this.boardingPass = boardingPass;
       this.idCard = idCard;
       this.count++;
       check();
}

修改后的 pass 方法,无论运行多久都不会再出现检查出错的情况了,为什么只在 pas 方法增加 synchronized 关键字, check 以及 toString 方法都有对共享资源的访问,难道它们不加同步就不会引起错误么?由于 check 方法是在 pass 方法中执行的,pass 方法加同步已经保证了 single thread execution,因此 check 方法不需要增加同步, toString 方法原因与此相同。

何时适合使用 single thread execution 模式呢?答案如下。

A. 多线程访问资源的时候,被 synchronized 同步的方法总是排他性的。

B. 多个线程对某个类的状态发生改变的时候,比如 Flightsecurity 的登机牌以及身 份证。

 在 Java 中经常会听到线程安全的类和线程非安全的类,所谓线程安全的类是指多个线 程在对某个类的实例同时进行操作时,不会引起数据不一致的问题,反之则是线程非安全的类,在线程安全的类中经常会看到 synchronized 关键字的身影

 Future 设计模式

Future 模式有点类似于商品订单。比如在网购时,当看重某一件商品事,就可以提交 订单,当订单处理完成后,在家里等待商品送货上门即可。或者说更形象的我们发送 Ajax 请求的时候,页面是异步的进行后台处理,用户无须一直等待请求的结果,可以继续浏览或 操作其他内容。

Master-Worker 设计模式 

Master- Worker 模式是常用的并行计算模式。它的核心思想是系统由两类进程协作工 作: Master 进程和 Worker 进程。 Master 负责接收和分配任务,Worker 负责处理子任 务。当各个 Worker-子进程处理完成后,会将结果返回给 Master,由 Master 做归纳和总 结。其好处是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量。

 具体代码实现逻辑图如下:

 生产者消费者设计模式

生产者和消费者也是一个非常经典的多线程模式,我们在实际开发中应用非常广泛的思 想理念。在生产消费模式中:通常由两类线程,即若干个生产者的线程和若干个消费者的线程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信。

 具体代码逻辑实现思路:

 Immutable 不可变对象设计模式

不可变对象一定是线程安全的。

关于时间日期 API 线程不安全的问题

想必大家对 SimpleDateFormat 并不陌生。SimpleDateFormat 是 Java 中一个非常 常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微 妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的, 在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。关于时间日期 API 的线程不安全问题直到 JDK8 出现以后才得到解决。

 关于线程不安全的代码示例如下:

package com.tong.chapter18.demo01;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
public class SimpleDateFormatThreadUnsafe {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
      // 初始化时间日期 API
      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
      // 创建任务线程,执行任务将字符串转成指定格式日期
      Callable<Date> task = () -> sdf.parse("20200808");
      // 创建线程池,数量为 10
      ExecutorService pool = Executors.newFixedThreadPool(10);
      // 构建结果集
      List<Future<Date>> results = new ArrayList<>();
      // 开始执行任务线程,将结果添加至结果集
      for (int i = 0; i < 10; i++) {
             results.add(pool.submit(task));
       }
      // 打印结果集中的内容
      // 在任务线程执行过程中并且访问结果集内容就会报错
      for (Future<Date> future : results) {
             System.out.println(future.get());
     }
      // 关闭线程池
        pool.shutdown();
    }
}

运行结果如下:

 我们先自己来解决一下这个问题,线程不安全,我给它放到 ThreadLocal 中是否可行呢?

package com.tong.chapter18.demo01;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 将每次需要格式转换的参数都放入 ThreadLocal 中进行
*/
public class DateFormatThreadLocal {
      private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
      public static Date convert(String source) throws ParseException {
         return df.get().parse(source);
    }
}

然后格式化日期代码如下:

package com.tong.chapter18.demo01;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
public class SimpleDateFormatThreadSafe {
     public static void main(String[] args) throws ExecutionException,InterruptedException {
        // 初始化时间日期 API
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        // 创建任务线程,执行任务将字符串转成指定格式日期
        //Callable<Date> task = () -> sdf.parse("20191020");
        // 使用 ThreadLocal 处理非线程安全
        Callable<Date> task = () -> DateFormatThreadLocal.convert("20191020");
        // 创建线程池,数量为 10
         ExecutorService pool = Executors.newFixedThreadPool(10);
        // 构建结果集
         List<Future<Date>> results = new ArrayList<>();
        // 开始执行任务线程,将结果添加至结果集
         for (int i = 0; i < 10; i++) {
               results.add(pool.submit(task));
          }
        // 打印结果集中的内容
        // 在任务线程执行过程中并且访问结果集内容就会报错
         for (Future<Date> future : results) {
               System.out.println(future.get());
          }
         // 关闭线程池
         pool.shutdown();
    }
}

上面的程序不管运行多少次都不会再出现线程不安全的问题。

定义不可变对象的策略

如何定义不可变对象呢?官方文档描述如下:

 参考官网文档后设计一个不可变对象,如下:

package com.tong.chapter18.demo02;
public final class Person {
       private final String name;
       private final String address;

       public Person(final String name, final String address) {
            this.name = name;
            this.address = address;
        }

       public String getName() {
             return name;
        }

       public String getAddress() 
            return address;
        }
@Override
public String toString() {
       return "Person{" + "name='" + name + '\'' + ", address='" + address + '\'' + '}';
  }
}

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

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

相关文章

第一堂棒球课品牌设计·棒球1号位

需求背景 第一堂棒球课作为国内的创新体育课程&#xff0c;旨在为广大青少年提供一个更好的平台来接触并了解棒球这项运动。品牌的建设将影响到其在国内的认可度和知名度。在此背景下&#xff0c;我们的产品目标是为第一堂棒球课打造一个具有竞争力的品牌形象&#xff0c;满足…

基于jeecg-boot的nbcio-boot亿事达企业管理平台发布

目前这个演示系统与代码都同步&#xff0c;以后也尽量保持同步。 更多功能看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff…

「Java核心技术大会 2023」6月启动,邀你共同探讨Java生态

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

数据结构 | 图的最短路径 Floyd算法

一、数据结构定义 typedef int VertexType; typedef int EdgeType;/*图*/ typedef struct {VertexType Vexs[SIZE]; //结点 EdgeType Edges[SIZE][SIZE]; //权值 int vexnum, arcnum; }MGraph;/*路径*/ typedef struct {int path[SIZE][SIZE];EdgeType length; }Path; 1.二维…

给父组件添加点击事件,点击时获取父组件的target

我想获取class为 mes-it 的dom元素结果每次拿到的 target都是子元素 查了查资料可以用 pointer-events: none; 来规避子元素成为target 就是上面图片style 里面的样式

【图灵奖得主Frederick P.Brooks, Jr.带你走进软件工程的世界——《人月神话》】

畅销业界48年的传奇经典 被引频次最高的软工神话 本书为复杂项目管理提供了极具洞察力的见解、发人深省的观点以及大量的软件工程实践。 直至今天&#xff0c;《人月神话》依然活跃在软件开发各个领域的话题中&#xff01; 我是一个书狂&#xff0c;积习甚深&#xff0c;费尽心…

Python自动化测试五大框架(测试员收藏夹必备)

自2018年被评选为编程语言以来&#xff0c;Python在各大排行榜上一直都是名列前茅。目前&#xff0c;它在Tiobe指数中排名第三个&#xff0c;仅次于Java和C。随着该编程语言的广泛使用&#xff0c;基于Python的自动化测试框架也应运而生&#xff0c;且不断发展与丰富。 因此&am…

PyQt5调用Window弹窗,选择文件、选择多个文件、选择文件夹、保存文件

前言 本篇在讲什么 PyQt5调取windows选择和导出弹窗 本篇适合什么 适合初学Python的小白 适合使用pyqt5的开发项目 本篇需要什么 对Python语法有简单认知 依赖Python3.7环境 依赖Pycharm编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻理论&#xf…

01. 找到数组的中间位置 ——【Leetcode每日一题】

1991. 找到数组的中间位置 难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums &#xff0c;请你找到 最左边 的中间位置 middleIndex &#xff08;也就是所有可能中间位置下标最小的一个&#xff09;。 中间位置 middleIndex 是满足 nums[0] nums[1] … nums[m…

【dc-dc】降压恒压电源管理IC 平衡车控制器 电动车控制器 以太网交换机驱动芯片

产品 AP8851 一款宽电压范围降压型DC-DC 电源管理芯片&#xff0c;内部集成使能开关控制、基准电源、误差放大器、过热保护、限流保护、短路保护等功能&#xff0c;非常适合在宽输入电压范围具有优良的负载和线性调整度。AP8851 芯片包含每周期的峰值限流、软启动、过压保护和…

【计算机视觉 | 目标检测】术语理解9:AIGC的理解,对比学习,解码器,Mask解码器,耦合蒸馏,半耦合,图像编码器和组合解码器的耦合优化

文章目录 一、AIGC的理解二、对比学习三、解码器四、Mask解码器五、耦合蒸馏六、半耦合七、图像编码器和组合解码器的耦合优化 一、AIGC的理解 AIGC指的是使用人工智能技术自动生成的各类数字内容,包括文本、图像、音频、视频等。它利用机器学习模型进行智能化内容生成。 主要…

Oracle快速将A库的数据库对象同步到B库(包括数据)

1.在pl/sql中导出A的用户对象 2.导出表数据&#xff0c;直接导PDE文件 如果PDE不行的话就到选择第一个dmp 3.然后把用户B的对象重新创建一遍&#xff0c;数据导进去。 创建对象的时候table和sequence都要删掉重新创建&#xff0c;不然会报已存在。

你会合并数组吗?采用数组大小顺序排列合并C语言实现

第一个代码块进行的是两个数组进行合并&#xff0c;然后顺序排列出来定义两个数组里面各有几个元素然后采用循环输入相应的数字&#xff0c;这里注意是要在每一个数组中顺序排列的&#xff0c;也就是从大到小输入之后采用循环进行比较比较完跳出循环后因为两个数组中元素个数不…

【安装】安装MySQL 相关配置 Navicat 的使用入门 SQL语句初步

目录 安装MySQL选择custom选择MySQL Server选择服务安装路径和数据存储路径 配置MySQL配置端口号选择授权方式设置root用户的密码 Navicat建立连接新建数据库新建表添加字段设置主键 SQL语句DML&#xff08;重点&#xff09;DQL&#xff08;重点&#xff09;运算符其它函数增删…

视频去除水印怎么做?四个方法分享给你!

水印是在许多视频中常见的一种保护措施&#xff0c;但有时它有可能会妨碍我们对视频内容的欣赏。如果你想去除视频中的水印&#xff0c;下面将介绍四种简单有效的方法&#xff0c;让你轻松解决这个问题。 方法一&#xff1a;使用记灵在线工具 记灵在线工具是一款强大的在线视…

Shell通配符和正则表达式

目录 ​​​​​​​grep 通配符 正则表达式 grep grep家族有三大成员分别为&#xff1a; grep&#xff1a;支持使用基本正则表达式。 egrep&#xff1a;支持使用扩展正则表达式。 fgrep&#xff1a;不支持使用正则表达式&#xff0c;即所有的正则表达式中的元字符都将作…

mt管理器使用(app管理)

http://www.360doc.com/content/12/0121/07/13646414_997668971.shtml

常用的访问控制权限模型DAC RBAC

常用的访问控制权限模型DAC RBAC 文章目录 常用的访问控制权限模型DAC RBACLinux 自主访问控制与强制访问控制术语概念存取访问控制 Access Control自主访问控制强制访问控制 基于角色的权限控制模型RBAC模型管理方法RBAC0的管理命令RBAC0的系统支持方法RBAC0的高级审查持方法 …

❤️创意网页:经典透明登录页面(好看易学易用)

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

thinkphp模型递归查询

效果图&#xff1a; 查询代码&#xff1a; use app\model\Menu;function getMenuList(string $uid) {$list Menu::where(pid, $uid)->select();foreach ($list as $val) {$val[children] getMenuList($val->uid);}return $list; }function getMenuBelong(string $uid)…