10.4 Linux_并发_线程

news2024/11/20 20:30:37

概述

线程的共享资源:

可执行的指令、静态数据、文件描述符、当前工作目录、用户ID、用户组ID

线程的私有资源:

线程ID、程序计数器PC和相关寄存器、堆栈、错误号、优先级、执行状态和属性

线程编译:

gcc <.c文件> -l pthread -o <可执行文件>

线程的运行特点:

如果主线程main退出,则其他线程也被终止。因为main退出,进程的空间被释放,所有线程的空间也不复存在。

命令行查看线程状态:

ps -eLf | grep <程序名>

相关函数

1、创建线程

函数声明如下:

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr,
                   void *(*start_routine) (void *), 
                   void *arg);

返回值:成功返回0,失败返回错误码

thread:线程对象,将创建的新线程的TID号存入该变量

attr:线程的属性,NULL代表默认的属性。

         对于默认属性,线程占有的资源不会因执行结束而释放。

start_routine:线程的回调函数

arg:线程的回调函数的参数

2、线程退出

函数声明如下:

void pthread_exit(void *retval);

retval:线程退出时的返回值,该值可被pthread_join获取。

注意:当线程调用该函数后,线程结束,但线程的资源未被回收,最终资源被pthread_join回收。即:功能与exit退出进程类似

3、获取自己的线程ID(TID)

函数声明如下:

pthread_t pthread_self(void);

返回值:自己的TID号

4、线程回收

函数声明如下:

int pthread_join(pthread_t thread, void **retval);

返回值:成功返回0,失败返回错误码

thread:线程ID

retval:接收pthread_exit的返回值

注意:调用该函数是线程会进入阻塞状态,直到被等待的线程调用pthread_exit结束。被等待的线程结束后,pthread_join会回收退出线程的资源,并且解除阻塞

注意:pthread_join只能依次回收线程。比如先回收线程0,再回收线程1,如果这时线程0一直不结束,而线程1先结束,此时线程1不会被回收,而是等待线程0结束之后,线程1才回收。

5、线程分离

5.1 调用函数 

函数声明如下:

int pthread_detach(pthread_t thread);

返回值:成功返回0,失败返回错误码 

thread:线程ID

注意:调用该函数后指定的线程与主控线程断开关系,指定的线程结束后不会产生僵尸线程,即:自动回收资源。该函数可以由主线程调用,也可以由新创建的线程调用。

5.2 创建时设置线程属性 

函数声明如下:

//初始化线程属性变量attr
int pthread_attr_init(pthread_attr_t *attr);
//设置线程属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

attr: 线程属性

detachstate:分离状态,写 PTHREAD_CREATE_DETACHED

设置完成之后,将attr传入pthread_create的第二个参数attr即可在创建时完成线程分离。

6、取消线程

取消线程的作用是让主线程能够主动的让创建的线程进行退出,也可以让自己取消自己。

函数声明如下:

//取消线程
int pthread_cancel(pthread_t thread);
//手动设置取消点
void pthread_testcancel(void);
//设置取消状态
int pthread_setcancelstate(int state, int *oldstate);
//设置取消的时刻
int pthread_setcanceltype(int type, int *oldtype);

thread:线程ID

state:线程是否可以被取消

            允许被取消:PTHREAD_CANCEL_ENABLE(默认)

            禁止被取消:PTHREAD_CANCEL_DISABLE

type:线程取消时刻的模式

           等到取消点才取消:PTHREAD_CANCEL_DEFERRED(默认)

           目标线程会立刻取消:PTHREAD_CANCEL_ASYNCHRONOUS

oldstate:传入NULL

注意:线程的取消要有取消点,取消点就是阻塞的系统调用。当没有取消点时,该函数不会进入阻塞,并且对指定的线程也没有任何影响。

注意:调用pthread_cancel,指定的线程被取消,这意味着该线程在调用pthread_exit之前进行了退出。除此之外,pthread_cancel会自动回收线程的资源。

pthread_testcancel的使用:

该函数手动设置取消点,并不是说执行到该函数才设置,而是线程中存在这个函数,不论能否执行到,都可以进行取消。???????

pthread_setcancelstate的使用:

pthread_setcancelstate用于保护线程中的部分代码段完整执行,在这期间线程不可被取消。

使用方法如下:

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);

//线程中不可被取消的代码段

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

示例见示例代码:设置线程是否可以被取消

注意:在创建完线程之后,如果直接取消该线程,那么该线程中尽管在刚开始时就禁止取消,也有可能被取消。因为创建线程需要时间,当主线程取消线程时,新的线程还没有执行禁止取消的函数。

7、线程清理

线程清理的作用是释放线程取消之前申请的内存空间,这个释放代码需要自己编写。

函数声明如下:

void pthread_cleanup_push(void (*routine)(void *),void *arg);

void pthread_cleanup_pop(int execute);

routine:清理函数

arg:清理函数的参数

execute:写0代表不执行routine,非0代表执行routine

注意:这两个函数必须成对使用,并且pthread_cleanup_push后不加分号

注意:这两个函数可以多次使用,效果类似栈,先push的后pop

注意:这两个并不是函数,而是宏定义

routine被执行的情况:

  • 当主线程调用pthread_cancel取消了线程
  • 该线程调用了pthread_exit退出了线程
  • 执行到pthread_cleanup_pop,并且传参为非0

注意:当调用return退出线程时,routine不执行

示例代码

1、创建线程

具体代码实现如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

//线程0回调函数
void* pthread_0(void* arg){
	while(1){
		printf("this is pthread 0\n");
		sleep(1);
	}
	//退出线程
	pthread_exit(NULL);
}

//线程1回调函数
void* pthread_1(void* arg){
	while(1){
		printf("this is pthread 1\n");
		sleep(1);
	}
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid[2];//多个线程时,tid用数组存储

	//创建线程
	if(pthread_create(&tid[0],NULL,(void*)pthread_0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	if(pthread_create(&tid[1],NULL,(void*)pthread_1,NULL) != 0){
		perror("pthread_create");
		return -1;
	}

	while(1){
		printf("this is main\n");
		sleep(1);
	}
	return 0;
}

代码运行结果如下:

2、获取线程TID

具体代码实现如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

//线程回调函数
void* pthread_0(void* arg){
	//线程中查看自己的TID
	printf("pthread:tid0 = %ld\n",pthread_self());
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid;
	//创建线程
	if(pthread_create(&tid,NULL,(void*)pthread_0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	//main中查看所创建线程的TID
	printf("main:tid0 = %ld\n",tid);
	while(1);
	return 0;
}

代码运行结果如下:

3、参数传递

3.1 传递一个参数

传递一个参数,只需要将这个参数的地址进行传递即可。

具体代码实现如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

//线程回调函数
void* pthread_0(void* arg){
	//获取传入的参数
	printf("arg = %d\n",*((int*)arg));
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid;
	int arg0 = 100;
	//创建线程
	if(pthread_create(&tid,NULL,(void*)pthread_0,&arg0) != 0){
		perror("pthread_create");
		return -1;
	}
	while(1);
	return 0;
}

代码运行结果如下:

3.2 传递多个参数

传递多个参数,就是将多个参数捆为一个结构体,传入的是结构体的指针。

具体代码实现如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

//线程回调函数
struct test{
	int num;
	char a;
};
void* pthread_0(void* arg){
	//获取传入的参数
	printf("arg.num = %d\n",((struct test*)arg)->num);
	printf("arg.a = %c\n",((struct test*)arg)->a);
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid;
	struct test arg0 = {300,'a'};

	//创建线程
	if(pthread_create(&tid,NULL,(void*)pthread_0,&arg0) != 0){
		perror("pthread_create");
		return -1;
	}
	while(1);
	return 0;
}

代码运行结果如下:

3.3 传递的参数经常变化

在3.1、3.2中,传递的参数都是固定的,因此传递指针是可以的。但如果参数会在main中发送改变,那么线程在接收到指针后,访问内存的数据不一定是传入时的数据,即:访问时main已经把数据进行了修改。

针对这个问题,可以将数据直接强制转为void*,进行传递。

具体代码实现如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

//线程0回调函数
void* pthread_0(void* arg){
	printf("num:%d,tid = %ld\n",arg,pthread_self());
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid[10];//多个线程时,tid用数组存储
	int i;
	for(i=0;i<10;i++){
		//创建线程
		if(pthread_create(&tid[0],NULL,(void*)pthread_0,(void*)i) != 0){//将i强转为void*
			perror("pthread_create");
			return -1;
		}
	}
	while(1){
		printf("this is main\n");
		sleep(1);
	}
	return 0;
}

代码运行结果如下:

4、等待线程退出

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	int i;
	for(i=0;i<3;i++){
		printf("this is pthread0\n");
		sleep(1);
	}
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	//等待线程退出,main进入阻塞
	pthread_join(tid,&ret0);
	printf("ret0:%s\n",(char*)ret0);//打印线程0退出状态
	while(1);

	return 0;
}

代码运行结果如下:

5、线程分离

5.1 调用pthread_detach

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	
	//pthread_detach(pthread_self());//也可以在这里进行线程分离
	printf("this is pthread0\n");
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	pthread_t tid1;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		pthread_detach(tid);//线程分离
		return -1;
	}
	while(1);

	return 0;
}

5.2 设置线程属性为分离

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	printf("this is pthread0\n");
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	pthread_attr_t attr;
	//初始化线程属性
	pthread_attr_init(&attr);
	//设置线程属性为分离
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	//创建线程,创建时传入attr
	if(pthread_create(&tid,&attr,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	while(1);

	return 0;
}

6、取消线程

6.1 基本使用

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	while(1){
		sleep(1);//进入阻塞,这里就是一个取消点
        //pthread_testcancel();//也可以将阻塞换为该函数,手动设置一个取消点
	}
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	//取消线程
	pthread_cancel(tid);//取消之后pthread_exit没有运行
	pthread_join(tid,&ret0);//这里的ret0不会收到pthread_exit的返回值
	printf("get\n");
	while(1);

	return 0;
}

代码运行结果如下:

6.2 设置线程是否可以被取消 

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	int i;
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//线程禁止被取消
	for(i=0;i<3;i++){
		printf("now cannot cancel\n");
		sleep(1);
	}
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//线程可以被取消
	while(1){
		printf("now can cancel\n");
		sleep(1);
	}
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);//创建线程后不能立刻取消,需要一些时间让线程执行程序
	pthread_cancel(tid);//取消线程
	printf("get\n");
	pthread_join(tid,&ret0);
	printf("get2\n");
	while(1);

	return 0;
}

代码运行结果如下:

7、线程清理

7.1  routine执行情况1

当主线程调用pthread_cancel取消了线程

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"2") //这里不能加分号
	while(1){
		sleep(1);//这里阻塞当作一个取消点	
	}
	printf("after while\n");	
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_cancel(tid);//取消线程
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

7.2  routine执行情况2

该线程调用了pthread_exit退出了线程

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"1") //这里不能加分号
	
	pthread_exit(NULL);//线程退出
	printf("after exit\n");
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

7.3  routine执行情况3

执行到pthread_cleanup_pop,并且传参为非0

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"1") //这里不能加分号
	
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

7.4 多个清理函数

多次push后,先push的后pop。

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"1") //这里不能加分号
	pthread_cleanup_push(clean,"2") //这里不能加分号
	pthread_cleanup_push(clean,"3") //这里不能加分号
	
	pthread_cleanup_pop(1);
	printf("after pop\n");
	while(1){
		printf("now in while\n");
		sleep(1);//阻塞作为取消点 
	}
	printf("after while\n");
	pthread_exit(NULL);//线程退出
	printf("after exit\n");
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_cancel(tid);
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

代码运行结果如下:

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

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

相关文章

数据集-目标检测系列- 螃蟹 检测数据集 crab >> DataBall

数据集-目标检测系列- 螃蟹 检测数据集 crab >> DataBall 数据集-目标检测系列- 螃蟹 检测数据集 crab >> DataBall 数据量&#xff1a;3k 想要进一步了解&#xff0c;请联系。 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&a…

加密与安全_TOTP 一次性密码生成算法

文章目录 PreTOTP是什么TOTP 算法工作原理TOTP 生成公式TOTP 与 HOTP 的对比Code生成TOTP验证 TOTP使用场景小结 TOTP 与 HOTP 的主要区别TOTP 与 HOTP应用场景比较TOTP 与 HOTP安全性分析 Pre 加密与安全_HTOP 一次性密码生成算法 https://github.com/samdjstevens/java-tot…

YOLO11改进|卷积篇|引入可变核卷积AKConv

目录 一、AKConv卷积1.1AKConv卷积介绍1.2MLCA核心代码 五、添加MLCA注意力机制5.1STEP15.2STEP25.3STEP35.4STEP4 六、yaml文件与运行6.1yaml文件6.2运行成功截图 一、AKConv卷积 1.1AKConv卷积介绍 AKConv允许卷积参数的数量以线性方式增加或减少&#xff0c;而不是传统的平…

C# 表达式与运算符

本课要点&#xff1a; 1、表达式的基本概念 2、常用的几种运算符 3、运算符的优先级 4、常见问题 一 表达式 表达式是由运算符和操作数组成的。、-、*和/等都是运算符&#xff0c;操作数包括文本、常量、变量和表达式等。 二 算术运算符 2.1 算术运算符的使用 三 常见错误 …

Cocotb 学习记录--V01

1. Windows 下安装cocotb pip install cocotb 其他参考&#xff1a; 1.Welcome to cocotb’s documentation! — cocotb 1.9.1 documentation

【Koa】文件上传

主要使用两个 koa 插件&#xff0c;koa-body 里面自带文件上传功能&#xff0c;还有一个 koa-staitc 用于配置静态资源目录&#xff08;可以通过路径直接访问图片&#xff09;。 router const Router require(koa/router); const {upload} require(../controller/user);con…

Spring之生成Bean

Bean的生命周期&#xff1a;实例化->属性填充->初始化->销毁 核心入口方法&#xff1a;finishBeanFactoryInitialization-->preInstantiateSingletons DefaultListableBeanFactory#preInstantiateSingletons用于实例化非懒加载的bean。 1.preInstantiateSinglet…

【RADARSAT Constellation Mission(RCM)卫星星座简介】

RADARSAT Constellation Mission&#xff08;RCM&#xff09;卫星星座是加拿大太空局&#xff08;CSA&#xff09;的下一代C波段合成孔径雷达&#xff08;SAR&#xff09;卫星星座&#xff0c;以下是对其的详细介绍&#xff1a; 一、基本信息 发射时间&#xff1a;2019年6月…

Golang | Leetcode Golang题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; func findMinArrowShots(points [][]int) int {if len(points) 0 {return 0}sort.Slice(points, func(i, j int) bool { return points[i][1] < points[j][1] })maxRight : points[0][1]ans : 1for _, p : range points {if p[0] > …

秒懂Linux之线程

目录 线程概念 线程理解 地址空间&#xff08;页表&#xff0c;内存&#xff0c;虚拟地址&#xff09; 线程的控制 铺垫 线程创建 ​编辑 线程等待 线程异常 线程终止 代码 线程优点 线程缺点 线程特点 线程概念 线程是进程内部的一个执行分支&#xff0c;线程是C…

小程序-全局数据共享

目录 1.什么是全局数据共享 2. 小程序中的全局数据共享方案 MboX 1. 安装 MobX 相关的包 2. 创建 MobX 的 Store 实例 3. 将 Store 中的成员绑定到页面中 4. 在页面上使用 Store 中的成员 5. 将 Store 中的成员绑定到组件中 6. 在组件中使用 Store 中的成员 1.什么是全…

【LeetCode】每日一题 2024_10_2 准时到达的列车最小时速(二分答案)

前言 每天和你一起刷 LeetCode 每日一题~ 大家国庆节快乐呀~ LeetCode 启动&#xff01; 题目&#xff1a;准时到达的列车最小时速 代码与解题思路 今天这道题是经典的二分答案&#xff0c;结合这道题来讲就是&#xff0c;二分列车的速度 我最擅长的两个算法&#xff1a;一…

cGANs with Projection Discriminator

基于映射鉴别器的CGAN 模型中&#xff0c;判别器&#xff08;Discriminator&#xff09;不是通过将条件信息简单地与特征向量拼接&#xff08;concatenate&#xff09;来使用条件信息&#xff0c;而是采用一种基于投影的方式&#xff0c;这种方式更加尊重条件信息在底层概率模…

进程通信——内存映射

进程通信——内存映射 什么是内存映射 内存映射是一种将文件内容映射到进程地址空间的技术&#xff0c;使得进程可以直接访问文件内容&#xff0c;而不需要通过系统调用进行读写操作。内存映射可以提高文件访问的效率&#xff0c;并且可以实现进程间的通信。 内存映射的原理…

【HarmonyOS】时间处理Dayjs

背景 在项目中经常会使用要时间的格式转换&#xff0c;比如数据库返回一个Date数据&#xff0c;你需要转成2024-10-2的格式&#xff0c;鸿蒙的原生SDK中是没有办法实现的&#xff0c;因此&#xff0c;在这里介绍第三方封装好并且成熟使用的库Dayjs。 安装 切换到Entry文件夹下…

C++初学者指南-5.标准库(第二部分)–特殊迭代器

C初学者指南-5.标准库(第二部分)–特殊迭代器 文章目录 C初学者指南-5.标准库(第二部分)–特殊迭代器容器操纵器std::insert_iterator\<Container>std::back_insert_iterator\<Container>std::front_insert_iterator\<Container> I/O 流迭代器std::istream_i…

2024大二上js高级+ES6学习9.29(深/浅拷贝,正则表达式,let/const,解构赋值,箭头函数,剩余参数)

9.29.2024 1.浅拷贝和深拷贝 Es6的语法糖&#xff1a;用assign将obj对象浅拷贝给o对象。 把数组写在前面是因为数组也是对象 2.正则表达式 创建和检测正则表达式 正则表达式的使用直接跳过&#xff0c;等要用时现查现用 3.ES6 4.let关键字 块级作用域是指在一个{}l里 变量提…

Python | Leetcode Python题解之第441题排列硬币

题目&#xff1a; 题解&#xff1a; class Solution:def arrangeCoins(self, n: int) -> int:left, right 1, nwhile left < right:mid (left right 1) // 2if mid * (mid 1) < 2 * n:left midelse:right mid - 1return left

四、Java 基础语法

一、Java 的类、对象、方法和实例变量 一个 Java 程序可以认为是一系列对象的集合&#xff0c;而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。对象&#xff1a;对象是类的一个实例&#xff0c;有状态&#xff08;实例变量&#xff…

MySQL基础练习题49-低质量的问题

目录 题目 准备数据 分析数据 总结 题目 找出 低质量 问题的 ID 集合。如果一个力扣问题的喜欢率&#xff08;喜欢数除以总投票数&#xff09;严格低于 60% &#xff0c;则该问题为低质量问题。 按 problem_id 升序排列返回结果表。 准备数据 Create table If Not Exis…