synchronized到底锁的是谁、何时生效

news2024/11/17 19:49:17

一、synchronized锁的几种形式

  1. synchronized修饰普通方法
  2. synchronized修饰静态方法
  3. synchronized修饰代码块

1、synchronized修饰普通方法

简单示例

public class Test{

    private String age;
    private String name;

    public synchronized void print(String arg1, String arg2) {
        this.age = this.age + arg1;
        this.name = this.name + arg2;
    }
}

下面看一个稍微复杂的场景,main方法中启动两个线程,func1和func2均被synchronized修饰。
由于是非静态方法,锁的是当前对象data,由于func1和func2方法都被synchronized修饰,且在main方法中,是用的new出来的同一个data进行调用,于是锁的就是这个data,用到data发生资源抢占,需要排队。代码首先执行到func1,func1执行3秒,func2排队等待三秒后,执行func2。

public class Test{

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(data::func1).start();
        new Thread(data::func2).start();
    }
}

class Data{

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized void func1(){
        System.out.println("func1开始执行,当前时间"+ sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间"+sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间"+sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间"+ sbf.format(new Date()));
    }

    public synchronized void func2(){
        System.out.println("func2开始执行,当前时间"+ sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间"+ sbf.format(new Date()));
    }
}

在这里插入图片描述

但如果把func2方法的synchronized修饰去掉,那么运行结果如下。
因为去掉synchronized的func2不再参与data资源的抢占,main方法启动后,func的线程和func2的线程同时启动,首先到达线程1,但因为需要等待3秒,于是func2的结果率先输出,3秒后再输出func1的结果

public class Test{

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(data::func1).start();
        new Thread(data::func2).start();
    }
}

class Data{

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized void func1(){
        System.out.println("func1开始执行,当前时间"+ sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间"+sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间"+sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间"+ sbf.format(new Date()));
    }

    public void func2(){
        System.out.println("func2开始执行,当前时间"+ sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间"+ sbf.format(new Date()));
    }
}

在这里插入图片描述

此时再做一个变化,func1和func2仍然使用synchronized修饰,但main方法中调用func1与func2每次都是新new出来的Data对象,因为普通方法锁定的是同一个对象,而不同的对象直接不发生资源抢占,因此func2无需等待func1执行完毕再执行,而是和func1同步执行。

public class Test{

    public static void main(String[] args) {
        new Thread(new Data()::func1).start();
        new Thread(new Data()::func2).start();
    }
}

class Data{

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized void func1(){
        System.out.println("func1开始执行,当前时间"+ sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间"+sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间"+sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间"+ sbf.format(new Date()));
    }

    public synchronized void func2(){
        System.out.println("func2开始执行,当前时间"+ sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间"+ sbf.format(new Date()));
    }
}

在这里插入图片描述

2. synchronized修饰静态方法

简单示例

public class Test {

    public synchronized static void print(String arg1, String arg2) {
        System.out.println("静态方法锁");
    }
}

继续使用标题1中的例子,将func1及func2都修改为静态方法,然后在main方法中,new出来两个data对象分别调用,看下面的执行结果,发现虽然是两个对象,但仍然发生了资源抢占,因此synchronized修饰静态方法,锁定是当前类而不是当前对象。

同样的,去掉其中一个方法的synchronized修饰,则不会进行排队。

public class Test {

    public static void main(String[] args)  {
        new Thread(()->new Data().func1()).start();
        new Thread(()->new Data().func2()).start();
    }
}

class Data {

    static SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized static void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间" + sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间" + sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()));
    }

    public synchronized static void func2() {
        System.out.println("func2开始执行,当前时间" + sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间" + sbf.format(new Date()));
    }
}

在这里插入图片描述

3. synchronized修饰代码块

简单示例

public class Test{

    private String age;
    private String name;

    private Object object = new Object();
    
    public void print(String arg1, String arg2) {
        synchronized (object) {
            this.age = this.age + arg1;
        }
        this.name = this.name + arg2;
    }
}

继续用标题1中的例子修改,起5个线程,去调用func1,func1调用func2,且将func2包裹在同步代码块中,用synchronized修改,锁定this对象,而this指的就是当前Data对象的实例化对象,因此五个线程均发生了资源抢占,挨个按顺序执行。synchronized同步代码块可圈定最小的锁范围,提高效率。

public class Test {

    public static void main(String[] args) {
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    static SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定this对象
        synchronized (this) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
同样的,若将main方法中调用func1的对象,每次都新new出来一个采用不同的对象,则不会进行排队。

public class Test {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            // 每次都是不同的对象
            new Thread(new Data()::func1).start();
        }
    }
}

class Data {

    static SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定this对象
        synchronized (this) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
但将锁定对象换为new InstanceTest(),也没有触发排队,因为每次锁的对象都是新new出来的,锁的不是同一个,虽然调用的对象是同一个。

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new InstanceTest()对象
        synchronized (new InstanceTest()) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
将锁定对象InstanceTest移到func1方法外面变成Data类的成员变量:private InstanceTest instanceTest = new InstanceTest();,调用仍然是同一个data对象,因此Data类中new出来的InstanceTest也是同一个对象,因此会发生排队。

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    private InstanceTest instanceTest = new InstanceTest();

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定instanceTest对象
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
将锁定的对象换为Integer xxx = 1;因为Integer拆箱共享常量池,只有一份,则发生排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定Integer instanceTest = 1对象
        Integer instanceTest = 1;
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
但如果将Integer new出来对象,产生了多个不同的Integer对象,就不会排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new Integer()对象
        Integer instanceTest = new Integer(1);
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
将锁定的Integer instanceTest = 1;修改为Integer instanceTest = 128;不发生排队。因为jdk自动拆箱,–128到127之间的值在内存中会被重复利用,认为是一个对象,而超过这个范围的值将会被加载为多个对象,每来一次new一个Integer。因此未发生排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new InstanceTest()对象
        Integer instanceTest = 128;
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述

此时我锁类。因为类只有一个,因此会发生排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new Data()对象
        synchronized (Data.class) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述

二、结论

1. synchronized修饰普通方法

锁定的是当前对象,当前方法的调用对象若是new出来的多份,则不发生资源抢占;只有同一个对象调用该方法时,synchronized锁才生效。

2. synchronized修饰静态方法

锁定是当前类,不论是用类调用的该方法,还是用new出来的一个或N个对象调用的该方法,都会发生资源抢占。

3. synchronized修饰代码块

锁定的是括号中的对象或类。可以认为是结合了1与2的优点与方式。括号中的对象若是一份,则synchronized锁生效,否则无效。生效情况如下:

  • 括号中的对象为this,且调用该方法的对象为同一个
  • 不论调用该方法的对象是不是同一个,括号中的对象永远是同一个,比如:Integer xxx = 1;
  • 括号中为类

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

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

相关文章

零代码,让业务人员实现应用创造自由

摘要&#xff1a;以汽车营销场景为例&#xff0c;从AppCube零代码和业务大屏入手&#xff0c;帮助开发者更好地理解AppCube低代码和零代码异同点&#xff0c;在实际使用时能更快选取更合适的工具能力&#xff0c;实现应用构建效率最大化。本文分享自华为云社区《DTT第8期直播回…

超级详细的Vue安装与配置教程

Vue web前端三大主流框架之一,是一套用于构建用户界面的渐进式框架,下面这篇文章主要给大家介绍了关于Vue安装与配置教程的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下 − 目录 一、下载和安装Vue二、创建全局安装目录和缓存日志目录三、配置环境变量 1. 环境…

【k哥爬虫普法】简历大数据公司被查封,个人隐私是红线!

我国目前并未出台专门针对网络爬虫技术的法律规范&#xff0c;但在司法实践中&#xff0c;相关判决已屡见不鲜&#xff0c;K 哥特设了“K哥爬虫普法”专栏&#xff0c;本栏目通过对真实案例的分析&#xff0c;旨在提高广大爬虫工程师的法律意识&#xff0c;知晓如何合法合规利用…

mysql忘记密码怎么办(附免密登录和修改密码)

前言 博主个人社区&#xff1a;开发与算法学习社区 博主个人主页&#xff1a;Killing Vibe的博客 欢迎大家加入&#xff0c;一起交流学习~~ 一、打开MySQL&#xff08;能打开请跳过此步&#xff09; 第一种&#xff1a;安装完MySQL之后&#xff0c;MySQL提供大家的客户端程序 …

DASCTF X GFCTF 2022十月挑战赛-hade_waibo

这是一个非预期解&#xff0c;但是得到出题人的赞许&#xff0c;莫名开心&#xff0c;哈哈&#xff1a; cancan need处存在任意文件读取 <!DOCTYPE html> <html lang"en" class"no-js"> <head> <meta charset"UTF-8" />…

引爆记忆广告语盘点

在数字化、流量红利见顶、营销环境巨变的进程中&#xff0c;品牌传播的节奏从快到稳。品牌出圈更需要产品、渠道、内容、文化等方面的共振影响&#xff0c;其中广告语作为品牌定位和价值主张的核心载体&#xff0c;是连接消费者心智的重要品牌资产。 根据益普索Ipsos《引爆记忆…

uni-app、小程序项目分包经验之谈与天坑异常:RangeError: Maximum call stack size exceeded

小程序分包经验之谈与天坑异常&#xff1a;RangeError: Maximum call stack size exceeded小程序分包概述分包配置参数&#xff1a;subPackages分包预载配置参数&#xff1a;preloadRule如何使用实际小程序项目分包项目结构配置分包配置分包预载天坑异常场景分析猜想尝试解决解…

springboot配置多个数据源

一.多数据源的典型使用场景 在实际开发中,经常可能遇到在一个应用中可能要访问多个数据库多的情况,以下是两种典型场景 1.业务复杂 数据分布在不同的数据库中,数据库拆了,应用没拆.一个公司多个子项目,各用各的数据库,设计数据共享 2.读写分离 为了解决数据库的性能瓶颈(读…

C++内存管理和模板

目录 内存管理 new T[N] new和delete关键字的总结&#xff1a; 定位new表达式(placement-new)&#xff1a; 作用&#xff1a; 使用格式&#xff1a; 使用场景&#xff1a; 实例&#xff1a; 调用析构函数的两个方法&#xff1a; 池化技术&#xff1a; 面试题&#xff1…

Unity 分享 功能 用Unity Native Share Plugin 实现链接、图片、视频等文件的分享+ 安卓 Ios 都可以,代码图文详解

Unity 分享 功能 用Unity Native Share Plugin 实现链接、图片、视频等文件的分享 安卓 Ios 都可以&#xff0c;代码图文详解前言环境效果一、Unity Native Share Plugin导入1.下载2.导入二、案例1.分享文字1.脚本2.发包注意2.分享视频1.完善下刚才的脚本2.给复制按钮添加点击事…

【Linux】Linux环境搭建

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;Linux的介…

【QT 自研上位机 与 STM32F103下位机联调>>>通信测试-基础样例-联合文章】

【QT 自研上位机 与 STM32F103下位机联调>>>通信测试-基础样例-联合文章】1、概述2、实验环境3、联合文章&#xff08;1&#xff09;对于上位机&#xff0c;可以参照如下例子&#xff08;2&#xff09;对于下位机&#xff0c;可以参照如下例子4、QT上位机部分第一步&a…

python中os库的使用

目录介绍1 listdir(path: str)2 path.isdir(path: str)3 path.isfile(path: str)4 path.join(path: str, file: str)5 path.getsize(path: str)介绍 本博客记录python中os库的一些函数使用。 1 listdir(path: str) listdir()函数输入一个目录&#xff0c;返回该目录下的所有…

web前端 html+css+javascript游戏网页设计实例 (网页制作课作业)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

数字化浪潮下,低代码能否加速企业的数字化转型

随着加快建设数字中国的目标明确下来&#xff0c;市场上与数字化相关的企业都得到了极大鼓舞&#xff0c;这不仅意味着后续数字领域的加速发展&#xff0c;更是代表着数字化已经完全可以向各行各业拓展&#xff0c;大力推进数字化建设。数字中国也说明了数字化并不能只是限制在…

FastTunnel Win10内网穿透实现远程桌面

目录 一、需求 二、购买公网服务器 三、远程公网服务器 四、FastTunnel 的使用 1.下载 FastTunnel 2.启动服务器端 3.启动客户端 五、测试 六、安装服务 结束 一、需求 FastTunnel 简介 高性能跨平台内网穿透工具&#xff0c;使用它可以实现将内网服务暴露到公网供…

【数据结构与算法】时间复杂度和空间复杂度

✨ 个人主页&#xff1a;bit me ✨ 当前专栏&#xff1a;数据结构 &#x1f31f;每日一语&#xff1a;窗外有风景 笔下有前途 低头是题海 抬头是鹏程 时间复杂度和空间复杂度的认知&#x1f30e; 一. 如何衡量一个算法的好坏&#x1f319; 二. 算法效率&#x1fa90; 三. 时间…

Word处理控件Aspose.Words功能演示:在 Python 中将 TXT 文件转换为 PDF

各种人使用记事本以TXT格式记下重点或快速创建笔记。此外&#xff0c;TXT 文件用于在各种应用程序中存储纯文本。但是&#xff0c;由于记事本不提供高级功能&#xff0c;因此 TXT 文件通常会转换为PDF。为了以编程方式自动将 TXT 转换为 PDF&#xff0c;本文介绍了如何在 Pytho…

WEB API 接口签名sign验证入门与实战

目录参考什么是加解密加密方式分类对称加密技术非对称加密技术&#xff08;RSA加密算法&#xff09;&#xff08;数字证书&#xff09;场景1&#xff1a;公钥加密&#xff0c;私钥解密场景2&#xff1a;秘钥加密&#xff1a;数字签名&#xff0c;公钥解密&#xff1a;验证签名M…

从位运算理解位图

位图是一种较难理解的数据结构&#xff0c;想了解位图&#xff0c;我需要先温习一下基础&#xff0c;复习下一些二进制的知识 位运算 1个字节8个二进制位 二进制每逢二进一&#xff0c;下面是二进制对应的十进制转换方式 二进制十进制0000 00012^010000 00102^120000 00112…