当前位置: 首页 > news >正文

zookeeper全系列学习之分布式锁实现

文章目录

  • 前言
  • 一、分布式锁的通用实现思路
  • 二、ZK实现分布式锁的思路
  • 三、ZK实现分布式锁的编码实现
    • 1、核心工具类实现
    • 2、测试代码编写
      • 线程安全问题复现
      • 使用上面封装的`ZkLockHelper`实现的分布式锁
    • 优点
    • 缺点
  • 总结

前言

就像上篇文章zookeeper全系列学习之统一配置获取说的,有了naocs谁还用zk做配置中心呢一样,现在项目中用zk实现分布式锁的估计也很少了,但是我认为它其实是有存在的价值的,因为它的临时顺序节点的特点,当客户端不可用时他能及时识别从而避免客户端开线程去主动删除,无论是为了学习还是工作亦或是为了拓展知识面,我们还是了解下为好,下面开始正文


一、分布式锁的通用实现思路

分布式锁的概念以及常规解决方案可以参考之前的博客:聊聊分布式锁的解决方案;今天我们先分析下分布式锁的实现思路;

  • 首先,需要保证唯一性,即某一时点只能有一个线程访问某一资源;比方说待办短信通知功能,每天早上九点短信提醒所有工单的处理人处理工单,假设服务部署了20个容器,那么早上九点的时候会有20个线程启动准备发送短信,此时我们只能让一个线程执行短信发送,否则用户会收到20条相同的短信;
  • 其次,需要考虑下何时应该释放锁?这又分三种情况,一是拿到锁的线程正常结束,另一种是获取锁的线程异常退出,还有种是获取锁的线程一直阻塞;第一种情况直接释放即可,第二种情况可以通过定义下锁的过期时间然后通过定时任务去释放锁;zk的话直接通过临时节点即可;最后一种阻塞的情况也可以通过定时任务来释放,但是需要根据业务来综合判断,如果业务本身就是长时间耗时的操作那么锁的过期时间就得设置的久一点
  • 最后,当拿到锁的线程释放锁的时候,如何通知其他线程可以抢锁了呢
    这里简单介绍两种解决方案,一种是所有需要锁的线程主动轮询,固定时间去访问下看锁是否释放,但是这种方案无端增加服务器压力并且时效性无法保证;另一种就是zk的watch,监听锁所在的目录,一有变化立马得到通知

二、ZK实现分布式锁的思路

  • zk通过每个线程在同一父目录下创建临时有序节点,然后通过比较节点的id大小来实现分布式锁功能;再通过zk的watch机制实时获取节点的状态,如果被删除立即重新争抢锁;具体流程见线图:

提示:需要关注下图里判断自身不是最小节点时的监听情况,为什么不监听父节点?原因图里已有描述,这里就不再赘述

三、ZK实现分布式锁的编码实现

1、核心工具类实现

通过不断的调试,我封装了一个ZkLockHelper类,里面封装了上锁和释放锁的方法,为了方便我将zk的一些监听和回调机智也融合到一起了,并没有抽出来,下面贴上该类的全部代码

package com.darling.service.zookeeper.lock;import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.platform.commons.util.StringUtils;import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;/*** @description:* @author: dll* @date: Created in 2022/11/4 8:41* @version:* @modified By:*/
@Data
@Slf4j
public class ZkLockHelper implements AsyncCallback.StringCallback, AsyncCallback.StatCallback,Watcher, AsyncCallback.ChildrenCallback {private final String lockPath = "/lockItem";ZooKeeper zkClient;String threadName;CountDownLatch cd = new CountDownLatch(1);private String pathName;/*** 上锁*/public void tryLock() {try {log.info("线程:{}正在创建节点",threadName);zkClient.create(lockPath,(threadName).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this,"AAA");log.info("线程:{}正在阻塞......",threadName);// 由于上面是异步创建所以这里需要阻塞住当前线程cd.await();} catch (InterruptedException e) {e.printStackTrace();}}/*** 释放锁*/public void unLock() {try {zkClient.delete(pathName,-1);System.out.println(threadName + " 工作结束....");} catch (Exception e) {e.printStackTrace();}}/*** create方法的回调,创建成功后在此处获取/DCSLock的子目录,比较节点ID是否最小,是则拿到锁。。。* @param rc        状态码* @param path      create方法的path入参* @param ctx       create方法的上下文入参* @param name      创建成功的临时有序节点的名称,即在path的后面加上了zk维护的自增ID;*                  注意如果创建的不是有序节点,那么此处的name和path的内容一致*/@Overridepublic void processResult(int rc, String path, Object ctx, String name) {log.info(">>>>>>>>>>>>>>>>>processResult,rx:{},path:{},ctx:{},name:{}",rc,path,ctx.toString(),name);if (StringUtils.isNotBlank(name)) {try {pathName =  name ;// 此处path需注意要写/zkClient.getChildren("/", false,this,"123");
//                List<String> children = zkClient.getChildren("/", false);
//                log.info(">>>>>threadName:{},children:{}",threadName,children);
//                // 给children排序
//                Collections.sort(children);
//                int i = children.indexOf(pathName.substring(1));
//                // 判断自身是否第一个
//                if (Objects.equals(i,0)) {
//                    // 是第一个则表示抢到了锁
//                    log.info("线程{}抢到了锁",threadName);
//                    cd.countDown();
//                }else {
//                    // 表示没抢到锁
//                    log.info("线程{}抢锁失败,重新注册监听器",threadName);
//                    zkClient.exists("/"+children.get(i-1),this,this,"AAA");
//                }} catch (Exception e) {e.printStackTrace();}}}/*** exists方法的回调,此处暂不做处理* @param rc* @param path* @param ctx* @param stat*/@Overridepublic void processResult(int rc, String path, Object ctx, Stat stat) {}/*** exists的watch监听* @param event*/@Overridepublic void process(WatchedEvent event) {//如果第一个线程锁释放了,等价于第一个线程删除了节点,此时只有第二个线程会监控的到switch (event.getType()) {case None:break;case NodeCreated:break;case NodeDeleted:zkClient.getChildren("/", false,this,"123");
//                // 此处path需注意要写"/"
//                List<String> children = null;
//                try {
//                    children = zkClient.getChildren("/", false);
//                } catch (KeeperException e) {
//                    e.printStackTrace();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                log.info(">>>>>threadName:{},children:{}",threadName,children);
//                // 给children排序
//                Collections.sort(children);
//                int i = children.indexOf(pathName.substring(1));
//                // 判断自身是否第一个
//                if (Objects.equals(i,0)) {
//                    // 是第一个则表示抢到了锁
//                    log.info("线程{}抢到了锁",threadName);
//                    cd.countDown();
//                }else {
//                    /**
//                     *  表示没抢到锁;需要判断前置节点存不存在,其实这里并不是特别关心前置节点存不存在,所以其回调可以不处理;
//                     *  但是这里关注的前置节点的监听,当前置节点监听到被删除时就是其他线程抢锁之时
//                     */
//                    zkClient.exists("/"+children.get(i-1),this,this,"AAA");
//                }break;case NodeDataChanged:break;case NodeChildrenChanged:break;}}/*** getChildren方法的回调* @param rc* @param path* @param ctx* @param children*/@Overridepublic void processResult(int rc, String path, Object ctx, List<String> children) {try {log.info(">>>>>threadName:{},children:{}", threadName, children);if (Objects.isNull(children)) {return;}// 给children排序Collections.sort(children);int i = children.indexOf(pathName.substring(1));// 判断自身是否第一个if (Objects.equals(i, 0)) {// 是第一个则表示抢到了锁log.info("线程{}抢到了锁", threadName);cd.countDown();} else {// 表示没抢到锁log.info("线程{}抢锁失败,重新注册监听器", threadName);/***  表示没抢到锁;需要判断前置节点存不存在,其实这里并不是特别关心前置节点存不存在,所以其回调可以不处理;*  但是这里关注的前置节点的监听,当前置节点监听到被删除时就是其他线程抢锁之时*/zkClient.exists("/" + children.get(i - 1), this, this, "AAA");}} catch (Exception e) {e.printStackTrace();}}
}

提示:代码中注释的代码块可以关注下,原本是直接阻塞式编程,将获取所有子节点并释放锁的操作直接写在getChildren方法的回调里,后来发现当节点被删除时我们还要重新抢锁,那么代码就冗余了,于是结合响应式编程的思想,将这段核心代码放到getChildren方法的回调里,这样代码简洁了并且可以让业务更只关注于getChildren这件事了

2、测试代码编写

线程安全问题复现

package com.darling.service.zookeeper.lock;import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;/*** @description:  开启是个线程给i做递减操作,未加锁的情况下会有线程安全问题* @author: dll* @date: Created in 2022/11/8 8:32* @version:* @modified By:*/
@Slf4j
public class ZkLockTest02 {private int i = 10;@Testpublic void test() throws InterruptedException {for (int n = 0; n < 10; n++) {new Thread(new Runnable() {@SneakyThrows@Overridepublic void run() {Thread.sleep(100);incre();}}).start();}Thread.sleep(5000);log.info("i = {}",i);}/*** i递减 线程不安全*/public void incre(){
//        i.incrementAndGet();log.info("当前线程:{},i = {}",Thread.currentThread().getName(),i--);}
}
  • 上面代码运行结果如下:

使用上面封装的ZkLockHelper实现的分布式锁

package com.darling.service.zookeeper.lock;import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;/*** @description: 使用zk实现的分布式锁解决线程安全问题* @author: dll* @date: Created in 2022/11/8 8:32* @version:* @modified By:*/
@Slf4j
public class ZkLockTest03 {ZooKeeper zkClient;@Beforepublic void conn (){zkClient  = ZkUtil.getZkClient();}@Afterpublic void close (){try {zkClient.close();} catch (InterruptedException e) {e.printStackTrace();}}private int i = 10;@Testpublic void test() throws InterruptedException {for (int n = 0; n < 10; n++) {new Thread(new Runnable() {@SneakyThrows@Overridepublic void run() {Thread.sleep(100);ZkLockHelper zkHelper = new ZkLockHelper();// 这里给zkHelper设置threadName是为了后续调试的时候日志打印,便于观察存在的问题String threadName = Thread.currentThread().getName();zkHelper.setThreadName(threadName);zkHelper.setZkClient(zkClient);// tryLock上锁zkHelper.tryLock();incre();log.info("线程{}正在执行业务代码...",threadName);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 释放锁zkHelper.unLock();}}).start();}while (true) {}}/*** i递减 线程不安全*/public void incre(){
//        i.incrementAndGet();log.info("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆当前线程:{},i = {}",Thread.currentThread().getName(),i--);}
}
  • 运行结果如下:
    哈哈

由于日志中掺杂着zk的日志所有此处并未截全,但是也能看到i是在按规律递减的,不会出现通过线程拿到相同值的情况
#四、zk实现分布式锁的优缺点

优点

  • 集群部署不存在单点故障问题
  • 统一视图
    zk集群每个节点对外提供的数据是一致的,数据一致性有所报障
  • 临时有序节点
    zk提供临时有序节点,这样当客户端失去连接时会自动释放锁,不用像其他方案一样当拿到锁的实例服务不可用时,需要定时任务去删除锁;临时节点的特性就是当客户端失去连接会自动删除
  • watch能力加持
    当获取不到锁时,无需客户端定期轮询争抢,只需watch前一节点即可,当有变化时会及时通知,比普通方案即及时又高效;注意这里最好只watch前一节点,如果watch整个父目录的话,当客户端并发较大时会不断有请求进出zk,给zk性能带来压力

缺点

  • 与单机版redis比较的话性能肯定较差,但是当客户端集群足够庞大且业务量足够多时肯定还是集群更加稳定
  • 极端情况下还是会出现多个线程抢到同一把锁的问题;假设某个线程拿到锁后还没执行业务代码就进入长时间的垃圾收集STW了,此时与zk的连接也会消失;然后此时别的线程的watch会被触发从而抢到锁去执行了,但是当stw的线程恢复过来时继续执行自身的业务代码,此时就会出现不一致的问题了;当然,个人认为这种设想太过极端了,毕竟如果stw时间过长肯定会影响整个集群的性能的,所以我感觉可以不必考虑,真的要解决那么再加上mysql乐观锁吧;

总结

好了,zk实现分布式锁的编码实现就到这了,后续有时间再写redis、数据库实现的,其实思路缕清了,编码实现还是相对简单的

相关文章:

zookeeper全系列学习之分布式锁实现

文章目录 前言一、分布式锁的通用实现思路二、ZK实现分布式锁的思路三、ZK实现分布式锁的编码实现1、核心工具类实现2、测试代码编写线程安全问题复现使用上面封装的ZkLockHelper实现的分布式锁 优点缺点 总结 前言 就像上篇文章zookeeper全系列学习之统一配置获取说的&#x…...

耐用的内衣洗衣机有哪些?双11好用内衣洗衣机品牌排行榜

现代社会高速发展&#xff0c;人们对于生活品质的追求不断提高&#xff0c;内衣作为贴身衣物&#xff0c;其清洁程度直接关系到个人卫生和健康。因此&#xff0c;耐用且高效的内衣洗衣机成为了许多家庭的必需品。在双11购物节期间&#xff0c;众多品牌推出了各种优惠活动&#…...

富格林:曝光可信经验击败陷阱

富格林认为&#xff0c;现货黄金投资是一项收益与风险并存的交易活动。在现货黄金中&#xff0c;时常为投资者曝光总结一些可信的交易经验&#xff0c;能在必要时帮助投资者击败陷阱&#xff0c;同时也会获得较高概率的收益。如今的投资经验和策略是非常多的&#xff0c;以下是…...

3211、生成不含相邻零的二进制字符串-cangjie

题目 3211、生成不含相邻零的二进制字符串 思路 dfs 代码 class Solution {let numRune [r0, r1]func dfs(arr: ArrayList<Rune>, ans: ArrayList<String>,n: Int64):Unit{if(arr.size > n){ans.insert(0, String(arr))// println("insert ${String(…...

【wpf】wpf程序联合控制台测试

如果在wpf的工程里面&#xff0c;想通过控制台输出或者调试&#xff0c;可以点开项目属性&#xff0c;把输出输出类型改为控制台应用输出&#xff0c;这样调试程序时&#xff0c;wpf的界面和控制台界面都会同时打开&#xff0c;而且写的控制台代码都会有效&#xff01; 设置如…...

使用 Spring Doc 为 Spring REST API 生成 OpenAPI 3.0 文档

Spring Boot 3 整合 springdoc-openapi 概述 springdoc-openapi 是一个用于自动生成 OpenAPI 3.0 文档的库&#xff0c;它支持与 Spring Boot 无缝集成。通过这个库&#xff0c;你可以轻松地生成和展示 RESTful API 的文档&#xff0c;并且可以使用 Swagger UI 或 ReDoc 进行…...

ssm基于ssm框架的滁艺咖啡在线销售系统+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码请私聊我 需要定制请私聊 目 录 第1章 绪论 1 1.1选题动因 1 1.2目的和意义 1 1.3论文结构安排 2 第2章 开发环境与技术 3 2.1 MYSQ…...

微信小程序 - 动画(Animation)执行过程 / 实现过程 / 实现方式

前言 因官方文档描述不清晰,本文主要介绍微信小程序动画 实现过程 / 实现方式。 实现过程 推荐你对照 官方文档 来看本文章,这样更有利于理解。 简单来说,整个动画实现过程就三步: 创建一个动画实例 animation。调用实例的方法来描述动画。最后通过动画实例的 export 方法…...

【Linux】nohup 命令

【Linux】nohup 命令 1. 语法格式2. 实例3. 查找后台进程 nohup 英文全称 no hang up&#xff08;不挂起&#xff09;&#xff0c;用于在系统后台不挂断地运行命令&#xff0c;退出终端不会影响程序的运行。 nohup 命令&#xff0c;在默认情况下&#xff08;非重定向时&#x…...

CSS、Less、Scss

CSS、Less和SCSS都是用于描述网页外观的样式表语言&#xff0c;但它们各自具有不同的特点和功能。以下是对这三者的详细阐述及区别对比&#xff1a; 详细阐述 CSS&#xff08;Cascading Style Sheets&#xff09; 定义&#xff1a;CSS是一种用来表现HTML或XML等文件样式的计算机…...

[笔记] ffmpeg docker编译环境搭建

文章目录 环境参考dockerfile 文件步骤常见问题docker 构建镜像出现 INTERNAL_ERROR 失败? 总结 环境 docker 环境 系统centos 7.9 (无所谓了 你用docker编译就无所谓系统了) ffmpeg3.3 参考 https://blog.csdn.net/jiedichina/article/details/71438112 dockerfile 文件 …...

基于SSM的心理咨询管理管理系统(含源码+sql+视频导入教程+文档+PPT)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的心理咨询管理管理系统拥有三个角色&#xff1a;学生用户、咨询师、管理员 管理员&#xff1a;学生管理、咨询师管理、文档信息管理、预约信息管理、测试题目管理、测试信息管理…...

南开大学《2023年+2022年810自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《南开大学810自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2023年真题 2022年真题 Part1&#xff1a;2023年2022年完整版真题 2023年真题 2022年真题…...

【算法】Kruskal最小生成树算法

目录 一、最小生成树 二、Kruskal算法求最小生成树 三、代码 一、最小生成树 什么是最小生成树&#xff1f; 对于一个n个节点的带权图&#xff0c;从中选出n-1条边&#xff08;保持每个节点的联通&#xff09;构成一棵树&#xff08;不能带环&#xff09;&#xff0c;使得…...

Pocket通常指的是一种特定的凹形或凹槽

在几何和计算机辅助设计&#xff08;CAD&#xff09;中&#xff0c;“Pocket”通常指的是一种特定的凹形或凹槽&#xff0c;用于表示在物体表面上挖出的区域。其主要特点包括&#xff1a; 凹形区域&#xff1a;Pocket 是一个在三维模型中内凹的区域&#xff0c;通常从物体的表面…...

Cesium基础-(Entity)-(Billboard)

里边包含Vue、React框架代码 2、Billboard 广告牌 Cesium中的Billboard是一种用于在3D场景中添加图像标签的简单方式。Billboard提供了一种方法来显示定向的2D图像,这些图像通常用于表示简单的标记、符号或图标。以下是对Billboard的详细解读: 1. Billboard的定义和特性 B…...

从0到1,解读安卓ASO优化!

大家好&#xff0c;我是互金行业的一名ASO运营专员&#xff0c;目前是负责我们两个APP的ASO方面的维护&#xff0c;今天分享的内容主要是关于安卓ASO优化方案。 大致内容分为三块&#xff1a; 首先我要讲一下ASO是什么&#xff1b;接下来就是安卓的渠道的选择&#xff0c;安卓…...

go语言中流程控制语句

Go语言中的流程控制语句包括条件判断、循环和分支控制。以下是详细介绍&#xff1a; 1. 条件判断语句 if 语句 Go语言的 if 语句与其他语言类似&#xff0c;支持基本的条件判断。 if 条件 {// 执行代码 }if-else 语句&#xff1a; if 条件 {// 执行代码 } else {// 执行代码…...

k8s 部署 emqx

安装cert-manager 使用Helm安装 helm repo add jetstack https://charts.jetstack.io helm repo update helm upgrade --install cert-manager jetstack/cert-manager \--namespace cert-manager \--create-namespace \--set installCRDstrue如果通过helm命令安装失败&#x…...

CSS.导入方式

1.内部样式 在head的style里面定义如 <style>p1{color: brown;}</style> 2.内联样式 直接在标签的里面定义如 <p2 style"color: blue;">这是用了内联样式&#xff0c;蓝色</p2><br> 3.外部样式表 在css文件夹里面构建一个css文件…...

Linux之nfs服务器和dns服务器

NFS服务器 NFS&#xff08;Network File System&#xff0c;网络文件系统)&#xff0c;NFS服务器可以让PC将网络中的NFS服务器共享的目录挂载到本地端的文件系统中&#xff0c;而在本地端的系统 中看来&#xff0c;那个远程主机的目录就好像是自己的一个磁盘分区一样。 注&am…...

大模型系列——AlphaZero/强化学习/MCTS

AlphaGo Zero无需任何人类历史棋谱&#xff0c;仅使用深度强化学习&#xff0c;从零开始训练三天的成就已远远超过了人类数千年积累的围棋知识。 1、围棋知识 &#xff08;1&#xff09;如何简单理解围棋知识 &#xff08;2&#xff09;数子法分胜负&#xff1a;https://zhu…...

原生js实现拖拽上传(拖拽时高亮上传区域)

文章目录 drop相关事件说明-MDN演示代码&#xff08;.html) drop相关事件说明-MDN 演示 代码&#xff08;.html) <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"…...

python道格拉斯算法的实现

废话不多说 直接开干 需要用到模块 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple math #对浮点数的数学运算函数 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple shapely #提供几何形状的操作和分析&#xff0c;如交集、并集、差集等 pip install -i …...

STM32的hal库中,后缀带ex和不带的有什么区别

在STM32的HAL&#xff08;硬件抽象层&#xff09;库中&#xff0c;后缀带“ex”和不带“ex”的文件及其包含的内容存在显著的区别。这些区别主要体现在功能扩展性、使用场景以及API的层次上。 一、功能扩展性 不带“ex”后缀的文件&#xff1a; 这些文件通常包含标准的、核心…...

可观测性三大支柱

目录 可观测性成熟度模型 可观测性三大支柱的具体定义如下 指标 日志 链路 可观测性成熟度模型 可观测性成熟度模型&#xff0c;是一种用于衡量和评估企业软件系统内部可观测性的框架或方法&#xff0c;同 时也是一种用于反馈企业可观测性体系建设成熟度水平的框架或方法…...

【银河麒麟高级服务器操作系统·实例分享】裸金属服务器开机失败分析及处理建议

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 现象描述 裸金属物理服务器开机卡在EFI stub页面…...

模型剪枝实操

文章目录 实验报告&#xff1a;模型剪枝在图像分类任务中的应用摘要实验方法数据集和预处理模型架构剪枝过程实验设置 实验效果性能对比详细分析 结论 实验报告&#xff1a;模型剪枝在图像分类任务中的应用 摘要 本实验通过模型剪枝技术&#xff0c;对一个图像分类模型进行压…...

网安学习路线!最详细没有之一!看了这么多分享网安学习路线的一个详细的都没有!

零基础小白&#xff0c;到就业&#xff01;入门到入土的网安学习路线&#xff01; 在各大平台搜的网安学习路线都太粗略了。。。。看不下去了&#xff01; 我把自己报班的系统学习路线&#xff0c;整理拿出来跟大家分享了&#xff01;点击下图&#xff0c;福利&#xff01; …...

Ubuntu18.04安装vscode1.94.2失败安装vscode1.84.2

系统环境&#xff1a;Ubuntu18.04.6 LTS 自己先去vscode官网下载好最新版本的vscode1.94.2&#xff08;不下也行&#xff0c;反正最新版也用不了&#xff0c;哈哈&#xff09; 网址&#xff1a;Visual Studio Code - Code Editing. RedefinedVisual Studio Code is a code ed…...

东莞网站建设图表/方象科技专注于什么领域

2019独角兽企业重金招聘Python工程师标准>>> win10安装配置nodejs 下载node 官网下载node接下来在命令提示符里&#xff08;winR&#xff09;输入node -v和npm -v&#xff0c;如图所示&#xff0c;表示安装完成。测试&#xff0c; 配置node 在nodejs文件目录下&a…...

江油网站制作/百度指数搜索指数的数据来源

相信大家也能感受到&#xff0c;其实用多线程是很麻烦的&#xff0c;包括线程的创建、销毁和调度等等&#xff0c;而且我们平时工作时好像也并没有这样来 new 一个线程&#xff0c;其实是因为很多框架的底层都用到了线程池。线程池是帮助我们管理线程的工具&#xff0c;它维护了…...

阅读网站怎么做/头条新闻最新消息

1.21 简单的将书上代码敲了一遍。 非常顺利就过了。 1.22 就悲剧了。 先按书本的意思。代码非常快就写完了。但计算的时间在机子上漂浮不定。 3-5倍之间。 代码例如以下&#xff1a; (define (search-for-primes start end count) (define (timed-prime-test n)(newline) …...

君和网站建设/优化加速

转载于:https://www.cnblogs.com/fycn01/p/5852304.html...

学校网站怎么做优化/百度谷歌seo优化

list集合初始化的时候&#xff0c;若大小可知&#xff0c;应初始化固定大小的集合&#xff0c;也是个好习惯。 避免在add()元素的时候&#xff0c;要扩容&#xff0c;然后就得重新copy一遍元素&#xff0c;这样就不好啦。 ArrayList集合大概原理&#xff1a;就是一个Object数组…...

云服务器做网站视屏/关键词seo优化排名

maven官网&#xff0c;不同后缀文件的区别下载官网&#xff1a;https://maven.apache.org/download.cgi 首先弄清楚各后缀的含义&#xff1a; bin代表二进制class文件(由java文件编译而成)src代表源码&#xff08;java源码&#xff09;&#xff0c;源码source比binary大一些&…...