《Linux从练气到飞升》No.27 Linux中的线程互斥

news2024/10/7 12:24:27

🕺作者: 主页

我的专栏
C语言从0到1
探秘C++
数据结构从0到1
探秘Linux
菜鸟刷题集

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

文章目录

    • 前言
    • 1 进程线程间的互斥相关背景概念
    • 2 线程安全
    • 3 线程不安全
      • 3.1 线程不安全(看看猪跑
    • 4 互斥量mutex
      • 4.1 为什么可能无法获得争取结果?
      • 4.2 怎么解决?
    • 5 互斥锁
      • 5.1 什么是互斥锁
      • 5.2 逻辑梳理
      • 5.3 加锁逻辑
    • 6 互斥锁的接口
      • 6.1 初始化互斥锁
      • 6.2 销毁互斥锁
      • 6.3 互斥量的加锁和解锁
      • 6.4 互斥锁的使用
    • 7 死锁
      • 7.1 死锁的定义
      • 7.2 死锁产生的条件
      • 7.3 预防死锁
      • 7.4 避免死锁
      • 7.5 死锁预防和死锁避免之间的区别
      • 7.6 避免死锁的算法

前言

当谈到多线程编程时,线程互斥是一个至关重要的概念。在多线程环境下,确保共享资源的安全访问是至关重要的,而线程互斥正是为此而设计的。通过线程互斥,我们能够确保在任意给定时间内,只有一个线程能够访问共享资源,从而避免竞态条件和数据损坏。

在本篇博客中,我们将探讨线程互斥的重要性、实现线程互斥的方法以及在实际编程中如何应用线程互斥来确保多线程程序的正确性和稳定性。通过深入了解线程互斥,我们可以更好地理解多线程编程中的关键概念,提高程序的可靠性和性能。

希望本篇博客能够帮助你更好地理解线程互斥,并为你在多线程编程中遇到的挑战提供一些思路和解决方案。让我们一起深入探讨线程互斥,为构建高效、稳定的多线程程序打下坚实的基础。

1 进程线程间的互斥相关背景概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

2 线程安全

多个线程并发同一段代码时不会出现不同的结果,多个执行流访问临界资源不会导致程序产生二义性。

  • 执行流:可以理解为线程
  • 访问:指的是对临界资源进行操作
  • 临界资源:指的是多个线程都可以访问到的资源
  • 临界区:代码操作临界资源的代码区域称之为临界区
  • 二义性:相同的代码,结果不唯一

3 线程不安全

不安全、和上面就相反喽

  1. 假设一个场景:
    假设有一个CPU,两个线程,线程A和线程B,线程A和线程B都要对全局变量i(10)进行++操作
  2. 假设线程A先运行,但是线程A将i的值读取到寄存器之后,就被线程切换了。(操作系统会保存线程A的程序计数器和上下文信息)
  3. 假设B线程运行,正常继续++操作,那么i的值在内存中就被修改增加1了
  4. 此时线程A切换回来了,怎么计算?内存中i的值是多少?
  5. 结论:此时的i最终结果还是11,明明加了两次,但是却不符合逻辑,这就是不安全
  6. 怎么解决?这就需要用到互斥了,每次只允许一个线程进入修改,这样就不会有这种情况了

6f44ffc80d154eb998e5aaf484f98d17.png

3.1 线程不安全(看看猪跑

在我们想买票的时候,如果有两个人同时下单,它会发生什么呢?票是怎么发放的,会不会有两个人买到同一张票的情况?会不会有票数为负的情况?
我们通过代码来模拟一下,如果线程不安全时,抢票的情况
代码如下:
我们让4个线程来循环获取ticket,模拟抢票

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int ticket=1000;

void* get_ticket(void* arg)
{
    while(1)
    {
        if(ticket>0)
        {
            cout<<"i am "<<pthread_self()<<" get a ticket,no:"<<ticket<<endl;
            ticket--;
        }
        else 
        {
            break;
        }
    }
    return NULL;
}
int main()
{
    pthread_t tid[4];
    for(int i=0;i<4;i++)
    {
        int ret=pthread_create(&tid[i],NULL,get_ticket,NULL);
        if(ret!=0)
        {
            cout<<"线程创建失败!"<<endl;
        }

    }
    for(int i=0;i<4;i++)
    {
        pthread_join(tid[i],NULL);
    }

    cout<<"pthread_join end!"<<endl;
    return 0;
}

结果如下:
image.png
可以看到出现了负数的情况
image.png
甚至出现了两个线程抢到同一张票的情况

这就是所谓的线程不安全的情况

4 互斥量mutex

  • 大部分情况,线程使用的数据包都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属于单个线程,其他线程无法获得这种变量。
  • 但是很多时候,很多变量需要在线程间共享,这样的变量称为共享变量。通过数据的共享完成线程之间的交互。
  • 多个线程并发的操作共享变量会带来一些问题(就像前面代码中那样)

之前的抢票模拟,可以看到出现了负数的票,但是我们的条件中清楚的要求>0,这是为什么?

4.1 为什么可能无法获得争取结果?

  • if语句判断条件为真后,代码可以并发的切换到其他线程
  • –ticket操作本身就不是一个原子操作
取出ticket--部分的汇编代码
objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>
  • --ticket操作本身就不是一个原子操作,而是对应三条汇编指令:
    • load:将共享变量ticket从内存加载到寄存器中
    • update:更新寄存器里面的值,执行-1操作
    • store:将新值,从寄存器写回共享变量ticket的内存地址

4.2 怎么解决?

要解决上面的问题需要做到三点

  1. 代码必须要有互斥行为,当代码进入到临界区执行时,不允许其他线程进入该临界区
  2. 如果多个线程同时要求执行临界区代码,并且临界区没有其他线程在执行,那么只允许一个线程进入临界区
  3. 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点,只需要一把锁就行了,这把锁叫做互斥锁
image.png

5 互斥锁

5.1 什么是互斥锁

  • 互斥锁的底层就是一个互斥量,而互斥量的本质就是一个计数器,这个计数器的本质就只有两种情况 0 和 1
  • 1 表示当前临界资源可以被访问
  • 0 表示当前临界资源不可以被访问

5.2 逻辑梳理

拿之前的抢票的情况来说,如果加上这个锁,当一个线程想去访问临界资源,他得先获取互斥锁,如果此时互斥锁的值为1,则说明它可以访问,反之则不能,如果它正在访问临界资源,此时有第二个线程想来访问临界资源,发现互斥锁为0,它就不能进入,只能等待互斥锁为1时才能进入访问。这就保证当前的临界资源在同一时刻只能被一个执行流访问了。
但是需要注意的是,如果多个线程访问临界资源的时候是互斥访问的属性,一定要在多个线程中进行同一把锁的加锁操作,这样每个线程在访问临界资源之前都要获取这把锁,若锁的值为1就可以访问,反之则不能访问;如果给线程A加锁,但是不给线程B加锁,就会导致线程不安全的情况。
ebbd81970a3349d286674c8ba418f532.png

5.3 加锁逻辑

加锁的时候会提前在寄存器的计数器中保存的一个值 0,而不管内存的计数器中保存的值为多少,都会将寄存器中保存到值 0 和内存计数器中保存的值进行交换,然后对寄存器中的值进行判断是否为 1 ,如果为 1 ,则能加锁,如果不为 1 ,则不能加锁。
5b6f639cab594553a10749ecc2fb39c8.png
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令
该指令的作用是把寄存器和内存单元的数据相交换
由于只有一条指令,保证了原子性
即使是多处理器平台,访问内存的总线周期也有先后
一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。
现在我们把lock和unlock的伪代码给一下。
image.png

6 互斥锁的接口

6.1 初始化互斥锁

初始化互斥量有两种方法:

  1. 静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

  1. 动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrictattr);

返回值及参数说明:

  • 返回值:如果初始化成功则返回0 失败则返回错误码
  • 参数 mutex:需要初始化的互斥量
  • 参数 attr:初始化互斥量的属性一般设置为NULL即可

6.2 销毁互斥锁

image.png
需要注意的是:

  • 使用静态初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁互斥量
  • 已经销毁的互斥量要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex)

6.3 互斥量的加锁和解锁

  1. 阻塞加锁接口
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 如果互斥锁变量当中的计数器的值为1,调用该接口,则加锁成功,该接口调用完毕,函数返回
  • 如果互斥锁变量当中的计数器的值为0,调用该接口,则调用该接口的执行流阻塞在当前接口内部
  1. 非阻塞加锁接口
int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 不管有没有加锁成功,都会返回,所以需要对加锁返回的结果进行判断,判断是否加锁,如果加锁成功,则操作临界资源,反之则需要循环获取互斥锁,直到拿到互斥锁
  1. 带有超时时间的加锁接口
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

struct timespec{
	time_t tv_sec;//秒
	long tv_nsec;//纳秒
};
  • 需要搭配循环来使用,并且判断返回值
  1. 解锁接口
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 不管是阻塞加锁/非阻塞加锁/带有超时时间的加锁,加锁成功的互斥锁都可以使用该接口进行加锁。

image.png

6.4 互斥锁的使用

  1. 什么时候使用初始化互斥锁?

先初始化互斥锁,再创建线程

  1. 什么时候使用销毁互斥锁?

在所有使用互斥锁的线程全部退出之后就可以销毁互斥锁

  1. 什么时候使用加锁?

线程访问临界资源之前进行加锁操作

  1. 什么时候使用解锁?

线程所有退出的地方进行解锁

  1. 加锁之后不解锁会发生什么?

以之前的抢票为例

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int ticket=1000;

pthread_mutex_t g_lock; //全局变量的互斥锁

void* get_ticket(void* arg)
{
    while(1)//1位置加锁还是在2位置加锁
    {
        pthread_mutex_lock(&g_lock);
        //pos1
        if(ticket>0)
        {
            cout<<"i am "<<pthread_self()<<" get a ticket,no:"<<ticket<<endl; 
            //pos2
            ticket--;
        }
        else 
        {
            //将下面解锁的注释去掉就是正确的代码
            //pthread_mutex_unlock(&g_lock);
            break;
        }
        //pthread_mutex_unlock(&g_lock);
    }
    return NULL;
}
int main()
{
    pthread_mutex_init(&g_lock,NULL);//初始化互斥锁
    pthread_t tid[4];
    for(int i=0;i<4;i++)
    {
        int ret=pthread_create(&tid[i],NULL,get_ticket,NULL);
        if(ret!=0)
        {
            cout<<"线程创建失败!"<<endl;
        }

    }
    for(int i=0;i<4;i++)
    {
        pthread_join(tid[i],NULL);
    }

    cout<<"pthread_join end!"<<endl;
    pthread_mutex_destroy(&g_lock);
    return 0;
}

结果:
image.png
我们可以看到它只获取一张票就不再往下执行了,陷入了死锁中
这是因为有一个工作线程加锁之后没有进行解锁,其他线程再次去获取锁时,互斥锁中计数器中的值还是0,就要被阻塞等待,所以加锁之后一定要记得解锁
image.png

7 死锁

前面我们讲如果不进行解锁会造成死锁现象,但是死锁是什么?现在我们就来讲讲

7.1 死锁的定义

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于一种永久等待状态。
就像下图
线程A获取到互斥锁1,线程B获取到互斥锁2时,线程A和线程B同时还想获取对方手里的锁(线程A还想获取互斥锁2,线程B还想获取互斥锁1),此时就会导致死锁
image.png
代码实现看一下

#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;

pthread_mutex_t lock1;
pthread_mutex_t lock2;

void* ThreadNum1(void* args)
{
    (void*)args;
    pthread_mutex_lock(&lock1);
    sleep(3);
    pthread_mutex_lock(&lock2);
    return NULL;
}
void* ThreadNum2(void* args)
{
    (void*)args;
    pthread_mutex_lock(&lock2);
    sleep(3);
    pthread_mutex_lock(&lock1);
    return NULL;
}
int main()
{
    pthread_mutex_init(&lock1,NULL);
    pthread_mutex_init(&lock2,NULL);

    pthread_t tid;
    int ret = pthread_create(&tid,NULL,ThreadNum1,NULL);
    if(ret < 0)
    {
        cout<<"thread1 create failed"<<endl;
        return 0;
    }

    ret = pthread_create(&tid,NULL,ThreadNum2,NULL);
    if(ret < 0)
    {
        cout<<"thread2 create failed"<<endl;
        return 0;
    }

    while(1)
    {
        ;
    }
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
    return 0;
}

结果:
image.png
image.png
可以看到发生了死锁的现象

7.2 死锁产生的条件

死锁的生成有四个必要的条件:

  • 互斥条件:一个资源只能被一个执行流使用。(一个互斥锁只能被一个执行流在同一时刻拥有)
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。(就像前面的例子那样,各自都是各自所需要的资源,但是都不放手)
  • 不剥夺条件:一个执行流已获得的资源在未使用完之前不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。(互相等待,无限循环)

7.3 预防死锁

想要预防死锁只要破坏死锁4个条件中的一个即可

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

7.4 避免死锁

它和死锁预防的差别很小,可以把它理解为死锁预防的一种特例。
死锁避免策略在允许三个必要条件存在的条件下,来确保永远不会达到死锁点。
死锁避免方法有:

  • 若一个进程的请求会导致死锁,那么不启动该进程。
  • 若一个进程增加的资源请求会导致死锁,则不允许这个资源的分配。

相比死锁预防策略,死锁避免策略并发性更强。但是在使用中也有诸多限制:

  • 必须事先声明每个进程请求的最大资源
  • 分配的资源数量必须是固定的
  • 在占有资源时,进程不能够退出
  • 所讨论的进程的执行顺序必须没有任何同步要求的限制

7.5 死锁预防和死锁避免之间的区别

编号比较项预防死锁避免死锁
1概念预防死锁至少阻止了发生死锁的必要条件之一。避免死锁确保系统不会进入不安全状态
2资源请求预防死锁所有的资源都是一起请求的。资源请求是根据可用的安全路径完成的。
3所需信息预防死锁不需要关于现有资源、可用资源和资源请求的信息避免死锁需要关于现有资源、可用资源和资源请求的信息
4过程通过限制资源请求过程和资源处理来防止死锁。避免死锁会自动考虑请求并检查它是否对系统安全。
5抢占有时,抢占会更频繁地发生。避免死锁在死锁避免中没有抢占。
6资源分配策略用于防止死锁的资源分配策略是保守的。防止死锁的资源分配策略并不保守。
7未来的资源请求预防死锁不需要知道未来的进程资源请求。避免死锁需要了解未来的进程资源请求。
8优点预防死锁不涉及任何成本,因为它只需使条件之一为假,这样就不会发生死锁。由于此方法动态工作以分配资源,因此没有系统未充分利用。
9缺点死锁预防设备利用率低。避免死锁会使进程阻塞太久。
10使用示例假脱机和非阻塞同步算法。使用银行家和安全算法。

7.6 避免死锁的算法

  • 死锁检测算法:推荐博客《死锁的处理策略——检测和解除》
  • 银行家算法:推荐博客《银行家算法及其代码实现》

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

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

相关文章

springBoot 入门一 :创建springBoot项目

创建springBoot项目 配置maven 项目报错处理

MyBatis配置与映射文件深度解析

文章目录 MyBatis配置文件解析配置文件的组成部分配置数据源和数据库连接信息MyBatis的属性和类型别名 MyBatis映射文件详解映射文件的作用编写简单的映射文件resultMap和resultType的区别 结语 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &…

unity UGUI无限循环滚动居中

最近在做一个ui循环滚动的功能&#xff0c;网上找了半天脚本感觉都和我实际需求不太符合&#xff0c;自己花费一些时间完成了这个功能记录一下。下面开始正题 &#xff0c;我是采用unity自带组件Scroll View来完成&#xff0c;首先设置Scroll View如下图 面板层级结构如下 然…

zookeeper的安装部署

目录 简介 Zookeeper架构设计及原理 1.Zookeeper定义 2.Zookeeper的特点 3.Zookeeper的基本架构 4.Zookeeper的工作原理 5.Zookeeper的数据模型 &#xff08;1&#xff09;临时节点 &#xff08;2&#xff09;顺序节点 &#xff08;3&#xff09;观察机制 Zookeeper集…

ICC2/innovus merge gds

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 calibre merge gds的方法示例参考往期文章: Calibre Merge GDS ICC2: write_gds -merge_files "std.gds sram.gds io.gds ip.gds ... ..." innovus: streamout -…

Postman接口Mock Servier服务器

近期在复习Postman的基础知识&#xff0c;在小破站上跟着百里老师系统复习了一遍&#xff0c;也做了一些笔记&#xff0c;希望可以给大家一点点启发。 应用场景&#xff1a;后端的接口还没有开发完成&#xff0c;前端的业务需要调用后端的接口&#xff0c;可以使用mock模拟。 一…

开发板上网详细教程

开发板上网详细教程 PC端操作开发板操作 写在前面 今天想配置开发板的boa服务器&#xff0c;需要下载sudo apt-get install bison flex&#xff0c;但是一直报错&#xff0c;就蒙蔽了&#xff0c;后来想想真不应该啊&#xff0c;电脑和开发板通信没问题&#xff0c;但也只是如此…

VueEcharts的使用简解以及常用网站

目录 一&#xff1a;前言 二&#xff1a;实现 1、安装echarts依赖 2、创建图表 1&#xff09;全局引入 2&#xff09;按需引入 三&#xff1a;结尾 一&#xff1a;前言 VueEcharts 是项目开发中可视化的一个重要知识部分。其涵盖了柱状图&#xff0c;饼状图&#xff0c;…

LoadRunner脚本编写之三(事务函数)

关于脚本的这块&#xff0c;前两篇都在讲C语言&#xff0c;其实&#xff0c;要整理点实用的东西挺难&#xff0c;在应用中多对录制的脚本分析&#xff0c;但对于新手学脚本确实无从下手。 先贴一个脚本&#xff1a; 完整代码&#xff1a; 重点代码部分&#xff1a; Action(…

Redis快速入门(基础篇)

简介&#xff1a; 是一个高性能的 key-value数据库。 存在内存中 与其他 key-value 缓存产品有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可以将内存中的数据保持在磁盘中&#xff0c;重启的时候可以再次加载进行使用。 Redis不仅仅支持简单的key-value类…

浅了解下:运营商大数据如何挖掘电销同行网站,APP,精准获客 ?

今天我们要讲的是运营商精准大数据营销。运营商精准大数据营销只是精准营销的一种&#xff0c;精准营销筛选包含了电话营销这个词。那么电话营销如何通过运营商大数据找到精准的客户&#xff1f;电销如何通过大数据找到准确的客户来源&#xff1f; 在全网时代&#xff0c;大数…

TYUST-RM2023-NewMaker哨兵视觉代码

目录 1 TYUST-RM2023-NewMaker哨兵视觉代码 1.1 说明 1.2 算法设计 1.3 识别思路 TYUST-RM2023-NewMaker哨兵视觉代码 说明 本套代码是太原科技大学NewMaker战队2023赛季哨兵开源代码 本套代码主要含有&#xff1a;TYUST-RM2023赛季哨兵视觉代码&#xff0c;主要模块…

科研学习|科研软件——SPSS:卡方检验(交叉表)

第一步 打开SPSS软件&#xff0c;在工具栏中选中【打开-文件-数据】&#xff0c;然后选择一份要打开的数据表(如图所示)。 第二步 在工具栏中找到【分析-描述统计-交叉表】打开交叉表对话框(如图所示)。 第三步 接着将【行-列】相关变量放在对应对话框中(如图所示)。 第四步 在…

ubuntu 20.04+ORB_SLAM3 安装配库教程

目录 安装ros(如果只是运行ORB-SLAM3&#xff0c;可以跳过安装)0. ros 安装教程1. 安装opencv2. 安装Pangolin3. 安装Eigen34.安装Python & libssl-dev5.安装boost库6.安装ceres库&#xff08;不必须&#xff09;7.安装Sophus库&#xff08;不必须&#xff09;8. 安装g20库…

科研学习|研究方法——逻辑回归系数的显著性检验(python实现)

1. 背景 回归方程与回归系数的显著性检验 2. statsmodels 库 statsmodels库可以用来做逻辑回归、线性回归。并且会在summary中给出显著性检验的结果。最终我们想要的就是如下图的报告。 3. 计算过程 如果我们使用的sklearn构建的逻辑回归就没有办法直接输出这个报告&#xff0c…

Xocde 升级15 或者 iOS17报错:

错误&#xff1a; Assertion failed: (false && "compact unwind compressed function offset doesnt fit in 24 bits"), function operator(), file Layout.cpp, line 5758. 翻译&#xff1a; 断言失败&#xff1a;&#xff08;false&&“压缩展开…

基于安卓android微信小程序的食谱大全系统

项目介绍 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个开发过程首先对食谱大全进行需求分析&#xff0c;得出食谱大全主要功能。接着对食谱大全进行总体设计和详细设计。总体设…

窗口管理工具 Mosaic mac中文版功能特点

MosAIc mac是一种窗口管理工具&#xff0c;可帮助您在计算机屏幕上有效地组织和管理多个应用程序窗口。它提供了一种直观的方式来调整和排列窗口&#xff0c;以最大化工作效率。 MosAIc mac窗口管理软件功能和特点 窗口布局&#xff1a;MosAIc允许您选择不同的窗口布局&#x…

SketchUp (草图大师) SU2023 中文版软件安装包下载地址及安装教程!

1.鼠标右键【SketchUp2023(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;选择【解压到SketchUp2023(64bit)】。 2.打开解压后的文件夹&#xff0c;鼠标右键【Setup】选择【以管理员身份运行】。 3.点击【Next】。 4.点击Install Language右边…

JVM查看内存新生代老年代回收情况,排查oom

jstat 命令 jstat - [-t] [-h] [ []] option&#xff1a;我们经常使用的选项有gc、gcutil vmid&#xff1a;java进程id interval&#xff1a;间隔时间&#xff0c;单位为毫秒 count&#xff1a;打印次数 每秒打印一次 jstat -gc 9162 1000S0C:年轻代第一个survivor的容量…