JUC从实战到源码:悲观锁和乐观锁真正了解了吗

【JUC】- 多线程与锁的知识

😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页   @怒放吧德德  To记录领地
🌝分享学习心得,欢迎指正,大家一起学习成长!

转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人

在这里插入图片描述

文章目录

  • 【JUC】- 多线程与锁的知识
    • 前言
    • Java中锁的概念
    • 悲观锁、乐观锁
      • 悲观锁
      • 乐观锁
    • *Demo演示锁
      • 1 两个线程执行添加synchronized方法
      • 2 在sendEmail中进行延迟
      • 3 相同对象调用有/无锁方法
      • 4 两个对象调用带锁的两个方法
      • 5 一个对象两个静态同步方法
      • 6 两个对象两个静态同步方法
      • 7 一个对象一个静态同步方法一个同步方法
      • 8 两个对象一个静态同步方法一个同步方法
    • 补:函数接口集合:
    • 总结


前言

在之前的文章中,我们已经学到了关于Future以及演变到CompletableFuture的应用,接下来就是我们日常中接触最多的内容:多线程与并发,尽管之前笔者也发布过几篇文章,在CSDN的阅读量也都突破了20w+【多线程与高并发】- synchronized锁的认知,可见是大多数开发者都在关注的点。今天这篇文章属于JUC系列,然而也涉及到多线程与锁的知识,这章就再次来学习一下。

Java中锁的概念

在Java中,锁是一个非常重要的概念。它为我们提供了一种机制,能够在进行并发编程时解决线程间的同步问题。锁基本上是关于共享资源访问控制的一种机制。当一个线程试图访问共享数据时,它需要先获取到锁,然后才能进行操作。如果锁已经被其他线程持有,那么试图获取锁的线程将被阻塞,直到锁被释放。

内置锁:也被称为“监视器锁”或"synchronized锁",它是Java提供的锁机制的原始形式。当一个线程访问某个对象的synchronized方法或代码块时,它会自动获取这个锁。这种锁是非公平的,只有一个线程能够持有,直到其释放。
ReentrantLock: 是Java并发包(java.util.concurrent.locks)中提供的一种锁。它更为灵活,可以设置为公平或非公平的,还可以中断正在等待的线程,或者通过设置超时时间限制线程等待。ReentrantLock需要手动获取锁并释放,如果没有正确释放,就可能导致死锁。

对于synchronized的内容,笔者之前也沉淀了不少文章,这里对于重复的内容不会在继续赘述。以下我贴出一些阅读量较高的文章。

【多线程与高并发】- 线程基础与状态-CSDN博客
【多线程与高并发】- synchronized锁的认知
【多线程与高并发】- 浅谈volatile-CSDN博客
【多线程与高并发】- 锁的机制与底层优化原理_cpp锁底层-CSDN博客

以上文章的内容都是依次递增,建议读者顺序观看,能够很好的对锁有一定的认识。

悲观锁、乐观锁

在Java并发编程中,悲观锁和乐观锁是两种常见的锁定策略,它们对数据的并发访问具有不同的处理方式。接下来我们来学习悲观锁与乐观锁。

悲观锁

悲观锁假设最坏的情况,认为在数据被处理时总会有其它线程来竞争,所以在每次读写数据时都会先上锁,保证同一时间只有一个线程能操作数据,避免数据的冲突。这种锁的实现通常依赖于数据库的锁机制,如行锁、表锁等。Java中的synchronized关键字和Lock都可以看作是悲观锁的实现。

悲观锁的优点是能保证数据的一致性,但缺点是在高并发的场景下,获取锁的线程可能会长时间阻塞,导致并发能力下降。

那么悲观锁适用于什么场景呢?
悲观锁总是认为数据会被其他线程修改,因此在操作数据前总是先获取锁,确保当前线程对数据有独家访问权限。适合在写操作多的场景,通过先加锁来保证数据的准确性。

乐观锁

乐观锁则假设最好的情况,它认为在数据被处理时不会有其它线程来竞争。所以在读取数据时不会加锁,而在写入时才会检查在此期间有无其它线程对数据进行了修改。如果有,则操作失败并进行重试,否则写入数据。乐观锁的实现一般依赖于数据的版本号。每次读取数据时,都会读取数据的版本号,写入时检查版本号是否发生变化。如果版本号一致,则写入,并将版本号+1;如果版本号不一致,则重试。

乐观锁的优点是在并发度不高的场景下,减少了获取锁的开销,提高了并发能力。但在高并发场景下,数据争用激烈,可能需要频繁地进行重试,反而效率较低。

在Java中是通过无锁编程来实现,只是在更新的时候去做一个判断之前有没有别的线程更新了数据。
如果没有被其他线程更新,那么当前线程修改的数据就能写入成功。如果这个数据已经是被其他线程所更新,则就会更具不同的实现方式执行不同的操作,比如说放弃修改、重试抢锁等。
一般采用的规则有

版本号机制Version
CAS算法,Java原子类中的递增操作就是通过CAS自旋实现。

那么乐观锁适合哪种场景呢?
乐观锁适合在读操作多的场景,不加锁的特点会使得读操作的性能大幅度提升。这点不难想象,因为加锁是需要耗费性能的,但是读是不会有什么冲突的,只有写操作的介入才会导致数据问题。

*Demo演示锁

接下来我们用几个案例来演示锁相关的内容。
我们可以从阿里巴巴的开发手册中看一下,对于锁这方面的规定。
image.png
那么什么是对象锁,什么又是类锁,接下来将用具体的案例来了解。

1 两个线程执行添加synchronized方法

代码如下,首先准备一个资源类Phone,这是个很普通的方法,两个加了synchronized,但是没有进行锁争抢。这里加了睡眠100毫秒是为了让线程a先启动,这样有个顺序逻辑。

public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(phone::sendEmail, "thread-a").start();
        // 为了让a线程率先启动
        Thread.sleep(100);
        new Thread(phone::sendSMS, "thread-b").start();
    }
}

// 资源类
class Phone {

    public synchronized void sendEmail() {
        System.out.println("发送邮件");
    }

    public synchronized void sendSMS() {
        System.out.println("发送短信");
    }
}

最后的结果是输出了发送邮件,在输出发送短信。

发送邮件
发送短信

以上这个输出,我们可能会认为,会不会是因为sendEmail方法执行得比较快,而并不是sendSMS要等待sendEmail方法执行完之后才执行得呢?这个就得对synchronized有个很好得认识,这点我们结合以下第二个案例进行分析。

2 在sendEmail中进行延迟

这回做了个小改动,就是在调用执行发送邮件时候进行延时1秒。

public synchronized void sendEmail() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("发送邮件");
}

其他不改动,通过执行之后,我们可以看到,thread-b会等thread-a执行完毕后才执行。

发送邮件
发送短信

对比了第一第二个案例,第二个案例无非就是在sendEmail方法中加了延时一秒。但是通过执行,我们很好得看出输出的结果,就是sendSMS等待sendEmail方法的执行。
分析:

那么,为什么会出现这样呢?我们要理解,synchronized是把悲观锁,锁的是资源类而不是锁单个方法,当加了synchronized,不是只对这个方法进行加锁,而是在这个类资源中的所有加了synchronized关键字的方法都是只能有个线程来访问。此时就是类锁。

对于1、2案例的总结:

当synchronized加到了方法上,在一个对象中,所有加上了同步锁关键字的方法,只允许一个线程去访问,其他线程只能等待执行。锁的是对象this。

3 相同对象调用有/无锁方法

这个测试是只有一个对象,调用了一个是加了synchronized锁,一个不加锁。代码如下,还是让thread-a执行的是带有synchronized的方法,在此方法内延迟了1秒,在另一个方法没有加锁。

public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(phone::sendEmail, "thread-a").start();
        // 为了让a线程率先启动
        Thread.sleep(100);
        new Thread(phone::noSync, "thread-b").start();
    }
}

// 资源类
class Phone {

    public synchronized void sendEmail() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发送邮件");
    }

    public synchronized void sendSMS() {
        System.out.println("发送短信");
    }

    public void noSync() {
        System.out.println("这个方法没有加锁");
    }
}

很显然先输出没加锁方法的内容。

这个方法没有加锁
发送邮件

分析:
对比前面两种案例,这第三种虽然都是用了同一个对象,但是一个调用的是同步方法,另一个是普通方法,并不会让thread-b等待thread-a线程。

4 两个对象调用带锁的两个方法

这次我们修改一个方法,我们创建两个对象,分别执行sendEmail和sendSMS方法,这两个方法也是都加上了synchronized锁,sendEmail方法里面照样是延迟了1秒。

public static void main(String[] args) throws InterruptedException {
    Phone phone = new Phone();
    Phone phone2 = new Phone();
    new Thread(phone::sendEmail, "thread-a").start();
    // 为了让a线程率先启动
    Thread.sleep(100);
    new Thread(phone2::sendSMS, "thread-b").start();
}

最终得到的结果是sendSMS方法输出的内容。

发送短信
发送邮件

分析:
这种情况是使用了两个不同的对象,也就是说这是两把不同的锁,访问的资源是各自的,所以不会出现等待的情况。

5 一个对象两个静态同步方法

这回改变以下同步方法,将其设置为静态方法。

public static synchronized void sendEmail() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("发送邮件");
}

public static synchronized void sendSMS() {
    System.out.println("发送短信");
}

# 执行方法:
public static void main(String[] args) throws InterruptedException {
    Phone phone = new Phone();
//        Phone phone2 = new Phone();
    new Thread(()  -> {
        phone.sendEmail();
    }, "thread-a").start();
    // 为了让a线程率先启动
    Thread.sleep(100);
    new Thread(()  -> {
        phone.sendSMS();
    }, "thread-b").start();
}

通过执行后,我们能够观察到thread-b要等thread-a执行完后才会去执行。

发送邮件
发送短信

分析:
这种情况采用的是同一个对象,调用不同的方法,方法都是静态同步方法。然而会发生相互等待,那如果是两个不同对象呢?情况也还是会发生等待,这个在第六种情况会进行介绍。

6 两个对象两个静态同步方法

这次还是和上个代码一样,只是测试的时候,采用两个对象来执行各自方法。

public static void main(String[] args) throws InterruptedException {
    Phone phone = new Phone();
    Phone phone2 = new Phone();
    new Thread(()  -> {
        phone.sendEmail();
    }, "thread-a").start();
    // 为了让a线程率先启动
    Thread.sleep(100);
    new Thread(()  -> {
        phone2.sendSMS();
    }, "thread-b").start();
}

运行后,我们发现,phone2执行sendSMS也是需要等待phone执行sendEmail方法。
分析:
5、6两种情况都会发生等待,而这为什么呢?我想这就要我们了解static synchronized和synchronized两种方法是截然不同的。因为虽然通过new Phone出来的对象是两个不同的对象,但是在底层他们则是相同的模板,对于静态同步方法来说,锁的是模板。也就是说,5、6两种情况,他是把类锁。
5、6结论:

对于普通同步方法,锁的是当前实例对象,通常是this,也就是所有普通同步方法都是同意把锁,即对象本身,是把对象锁。
对于静态同步方法,锁的是当前类的Class对象,即是把类锁。
对于同步代码块,锁住的是synchronized括号里面的对象。

7 一个对象一个静态同步方法一个同步方法

接下来就是一个对象,一个静态同步方法(sendEmail)和一个同步方法(sendSMS)。通过一个对象调用两个方法。

public static void main(String[] args) throws InterruptedException {
    Phone phone = new Phone();
//        Phone phone2 = new Phone();
    new Thread(()  -> {
        phone.sendEmail();
    }, "thread-a").start();
    // 为了让a线程率先启动
    Thread.sleep(100);
    new Thread(()  -> {
        phone.sendSMS();
    }, "thread-b").start();
}

通过运行,我们发现,这回有所不一样,先输出了sendSMS的打印信息,也就是说thread-b线程不用等待thread-a执行完毕后才执行。

发送短信
发送邮件

分析:
两种锁,一个是类锁,一个是对象锁,两个加锁的类型不同,不产生资源竞争。

8 两个对象一个静态同步方法一个同步方法

接下来就是两个对象,一个静态同步方法(sendEmail)和一个同步方法(sendSMS)。phone对象调用sendEmail方法,phone2调用sendSMS方法。

public static void main(String[] args) throws InterruptedException {
    Phone phone = new Phone();
    Phone phone2 = new Phone();
    new Thread(()  -> {
        phone.sendEmail();
    }, "thread-a").start();
    // 为了让a线程率先启动
    Thread.sleep(100);
    new Thread(()  -> {
        phone2.sendSMS();
    }, "thread-b").start();
}

运行的结果还是先输出了sendSMS的打印信息,也就是说thread-b线程不用等待thread-a执行完毕后才执行。

发送短信
发送邮件

分析:
这种情况和第7种类似,只不过这次是使用了另一个对象的对象锁,与类锁还是不产生资源竞争。
总结:

所有的普通同步方法用的都是同一把锁一实例对象本身,就是new出来的县体实例对象本身,本类this,也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。
所有的静态同步方法用的也是同一把锁-类对象本身,就是我们说过的唯一板class,县体实例对象this和唯一模板class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竟态条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。

补:函数接口集合:

这里补充一下上小节对于CompletableFuture的学习的一个基础内容,想了想还是觉得有必要去了解一下关于函数接口的一些内容。
在Java中,函数接口(Functional Interface)是一种特殊的接口,它只有一个抽象方法。这种接口的主要目的是用于支持Lambda表达式和函数式编程。Java 提供了一些内置的函数接口,如FunctionPredicateConsumer等,同时也允许用户自定义函数接口。
以下是Java中一些常见的函数接口:

  1. Function<T, R>:接受一个参数并产生一个结果。它有一个抽象方法apply(T t),返回类型为R。
  2. Predicate<T>:接受一个参数并返回一个布尔值。它有一个抽象方法test(T t),返回类型为boolean。
  3. Consumer<T>:接受一个参数并执行某种操作,但不返回任何结果。它有一个抽象方法accept(T t),返回类型为void。
  4. Supplier<T>:不接受任何参数,但生成一个结果。它有一个抽象方法get(),返回类型为T。
  5. UnaryOperator<T>:接受一个参数并返回一个与输入类型相同的结果。它有一个抽象方法apply(T t),返回类型为T。
  6. BinaryOperator<T>:接受两个参数并返回一个与输入类型相同的结果。它有一个抽象方法apply(T t1, T t2),返回类型为T。
  7. BiFunction<T, U, R>:接受两个参数并产生一个结果。它有一个抽象方法apply(T t, U u),返回类型为R。
  8. BiPredicate<T, U>:接受两个参数并返回一个布尔值。它有一个抽象方法test(T t, U u),返回类型为boolean。
  9. BiConsumer<T, U>:接受两个参数并执行某种操作,但不返回任何结果。它有一个抽象方法accept(T t, U u),返回类型为void。
  10. IntConsumer:接受一个int类型的参数并执行某种操作,但不返回任何结果。它有一个抽象方法accept(int value),返回类型为void。
  11. LongConsumer:接受一个long类型的参数并执行某种操作,但不返回任何结果。它有一个抽象方法accept(long value),返回类型为void。
  12. DoubleConsumer:接受一个double类型的参数并执行某种操作,但不返回任何结果。它有一个抽象方法accept(double value),返回类型为void。

这些函数接口可以用于创建Lambda表达式,简化代码并提高可读性。例如,使用Function<Integer, Integer>接口可以将一个整数映射到另一个整数,如下所示:

Function<Integer, Integer> square = x -> x * x;
int result = square.apply(5); // result 为 25

总之,Java中的函数接口是一种强大的工具,可以帮助我们编写更简洁、更易读的代码。

总结

锁基本上是关于共享资源访问控制的一种机制。当一个线程试图访问共享数据时,它需要先获取到锁,然后才能进行操作。如果锁已经被其他线程持有,那么试图获取锁的线程将被阻塞,直到锁被释放。本片文章介绍了一些关于多线程与锁的部分知识点,了解了什么是悲观锁和乐观锁。重点是掌握synchronized加锁的一些需要真正认识的知识。也补充了上章没有介绍完全的内容-关于函数接口的集合。


转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

语音群呼之语音导航的应用

在数字化时代&#xff0c;语音群呼技术已成为企业、组织和个人高效沟通的重要工具。语音群呼不仅能够快速地将信息传递给目标群体&#xff0c;而且通过语音导航功能&#xff0c;还能确保信息传达的准确性和用户体验的优质性。本文将深入探讨语音群呼的语音导航功能&#xff0c;…

三菱M5-559 KURU TOGA advance断芯清理维修

三菱559自动铅笔使用过程中突然不出芯了&#xff0c;后面装芯出不来&#xff0c;前面插笔芯进不去&#xff0c;网上搜索&#xff0c;发现这支笔要按照下面的方法拆开清理&#xff0c;这里记录一下方便大家查看&#xff1a; 1、拧掉笔头外面的罩子。 2、要大胆一点&#xff0c…

图形学初识--深度测试

文章目录 前言正文为什么要有深度测试&#xff1f;画家算法循环遮挡 深度测试当代最常见实现方式&#xff1f;总述什么是z-buffer呢&#xff1f;z-buffer从哪来呢&#xff1f;如何利用z-buffer实现深度测试&#xff1f;举个例子 结尾&#xff1a;喜欢的小伙伴点点关注赞哦! 前言…

【MyBatis】MyBatis操作数据库(二):动态SQL、#{}与${}的区别

目录 一、 动态SQL1.1 \<if>标签1.2 \<trim>标签1.3 \<where>标签1.4 \<set>标签1.5 \<foreach>标签1.6 \<include>标签 二、 #{}与${}的区别2.1 #{}是预编译sql&#xff0c;${}是即时sql2.2 SQL注入2.3 #{}性能高于${}2.4 ${}用于排序功能…

SpringBoot案例,通关版

项目目录 此项目为了伙伴们可以快速入手SpringBoot项目,全网最详细的版本,每个伙伴都可以学会,这个项目每一步都会带大家做,学完后可以保证熟悉SpringBoot的开发流程项目介绍:项目使用springboot mybatis进行开发带你一起写小项目先把初始环境给你们第一步新建springboot项目返…

短剧出海的优势分析

海外短剧作为一种新兴的内容形式&#xff0c;正以其独特的魅力迅速占领市场&#xff0c;为企业带来了前所未有的商业机遇。本文将深入探讨短剧出海的优势&#xff0c;并为企业和老板们提供实用的操作指南。短剧出海是一个包含多个步骤的复杂过程&#xff0c;短剧出海需要综合考…

第100天:权限提升-数据库RedisPostgre第三方软件TV向日葵服务类

目录 思维导图 案例一: 数据库-Redis 数据库权限提升-计划任务 案例二: 数据库-PostgreSQL 数据库权限提升-漏洞 PostgreSQL 提权漏洞&#xff08;CVE-2018-1058&#xff09; PostgreSQL 高权限命令执行漏洞&#xff08;CVE-2019-9193&#xff09; 案例三: 三方应用-…

使用system verilog进行流水灯和VGA打印字符

使用system verilog进行流水灯和VGA打印字符 目录 **使用system verilog进行流水灯和VGA打印字符****system verilog的优点****VGA程序编写**VGA 控制器模块字符生成模块顶层模块测试基准程序**效果** **流水灯程序设计****效果** **总结** system verilog的优点 面向对象编程…

C# WinForm —— 27 28 29 30 ListView 介绍与应用

1. 简介 和ListBox的外观类似&#xff0c;都可以多列显示&#xff0c;但 ListView 功能更强大&#xff0c;提供了5种不同的显示方式 2. 属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到Enabled控件是否启用CheckBoxes复选框是否显示在项旁边ContextMenuStri…

浏览器渲染优--防抖节流懒加载

合理选择css选择器 相比于.content-title-span&#xff0c;使用.content .title span时&#xff0c;浏览器计算样式所要花费的时间更多。使用后面一种规则&#xff0c;浏览器必须遍历页面上所有 span 元素&#xff0c;先过滤掉祖先元素不是.title的&#xff0c;再过滤掉.title…

拿笔记下来!产品采购制造类合同怎样写比较稳妥?

拿笔记下来&#xff01;产品采购制造类合同怎样写比较稳妥&#xff1f; 近日&#xff0c;几经波折&#xff0c;泰中两国终于完成了潜艇采购谈判&#xff01;你知道吗&#xff1f;产品制造类合同或协议在起草前如果没有充分考虑各种因素&#xff0c;可能会导致一系列问题和不利…

奶茶店、女装店、餐饮店是高危创业方向,原因如下:

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 现在的俊男靓女们&#xff0c;心中都有一个执念&#xff1a; (1)想证明自己了&#xff0c;开个奶茶去…… (2)想多赚点钱了&#xff0c;加盟餐饮店去…… (3)工作不顺心了&#xff0c;搞个女装店去…… 但凡抱着…

【scau数据库实验一】mysql_navicat_数据库定义实验、基本命令

实验一开始之前&#xff0c;如果还有不会使用navicat建议花五分钟补课哦~ 补课地址&#xff1a;【scau数据库实验先导】mysql_navicat_数据库新建、navicat的使用-CSDN博客 实验目的&#xff1a; 理解和掌握数据库DDL语言&#xff0c;能够熟练地使用SQL DDL语句创建、修改和删…

mac电脑用谷歌浏览器对安卓手机H5页面进行inspect

1、mac上在谷歌浏览器上输入 chrome://inspect 并打开该页面。 2、连接安卓手机到Mac电脑&#xff1a;使用USB数据线将安卓手机连接到Mac电脑。 3、手机上打开要的h5页面 Webview下面选择要的页面&#xff0c;点击inspect&#xff0c;就能像谷歌浏览器页面打开下面的页面&#…

Vue——初识组件

文章目录 前言页面的构成何为组件编写组件组件嵌套注册 效果展示 前言 在官方文档中&#xff0c;对组件的知识点做了一个很全面的说明。本篇博客主要写一个自己的案例讲解。 vue 官方文档 组件基础 页面的构成 说到组件之前&#xff0c;先大致说明下vue中页面的构成要素。 在…

Claude 3可使用第三方API,实现业务流程自动化

5月31日&#xff0c;著名大模型平台Anthropic宣布&#xff0c;Claude3模型可以使用第三方API和工具。 这也就是说&#xff0c;用户通过文本提问的方式就能让Claude自动执行多种任务&#xff0c;例如&#xff0c;从发票中自动提取姓名、日期、金额等&#xff0c;该功能对于开发…

【问题随记】System policy prevents Wi-Fi scans,解决连接 WIFI 需要权限的问题

问题随记 System policy prevents Wi-Fi scans&#xff0c;每次打开我的开发板连接 wifi 都会出现下面的弹窗&#xff0c;这也阻挡了我的WIFI自动连接&#xff0c;然后就需要连上屏幕&#xff0c;输入 wifi 密码&#xff0c;这样才能进行 VNC、SSH 等一系列的连接。 问题解决 …

『 Linux 』缓冲区(万字)

文章目录 &#x1f9a6; 什么是缓冲区&#x1f9a6; 格式化输入/输出&#x1f9a6; 刷新策略&#x1fab6; 块缓冲(fully buffered)&#x1fab6; 无缓冲(unbuffered)&#x1fab6; 行缓冲(line buffered) &#x1f9a6; 现象解释&#x1f9a6; exit()与_exit()&#x1f9a6; 进…

CPU 使用率过高问题排查

文章目录 CPU 使用率过高问题排查1. CPU使用率过高常见问题2. 压力测试2.1 stress安装参数说明测试示例 2.2 stress-ng安装参数说明测试示例 3. 问题排查3.1 使用 top 命令3.2 使用 ps 命令3.3 使用 perf top3.4 vmstat 命令常用信息内存信息磁盘信息 CPU 使用率过高问题排查 …

Plotting World Map in Python

1. 方法一 pygal Plotting World Map Using Pygal in Python import pygal # create a world map worldmap pygal.maps.world.SupranationalWorld() # set the title of map worldmap.title Continents# adding the continents worldmap.add(Africa, [(africa)]) worl…