Linux-----线程同步(资源竞争和同步锁)

news2025/1/18 19:31:53

目录

资源竞争(背景)

 锁(解决方式,实现同步)

互斥锁

读写锁

自旋锁


资源竞争(背景)

竞态条件

当多个线程并发访问和修改同一个共享资源(如全局变量)时,如果没有适当的同步措施,就会遇到线程同步问题。这种情况下,程序最终的结果依赖于线程执行的具体时序,导致了竞态条件。

竞态条件(race condition)是一种特定的线程同步问题,指的是两个或者以上进程或者线程并发执行时,其最终的结果依赖于进程或者线程执行的精确时序。它会导致程序的行为和输出超出预期,因为共享资源的最终状态取决于线程执行的顺序和时机。为了确保程序执行结果的正确性和预期一致,需要通过适当的线程同步机制来避免竞态条件。

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

#define COUNT 20000

long long int num = 0;

void* fun(void *arg) {
    for (int i = 0;i < 1000000;i++) { // 加100w
        num++;
    }
}

int main(int argc, char const* argv[])
{
    pthread_t tid[2];

    pthread_create(&tid[0], NULL, fun, NULL);

    pthread_create(&tid[1], NULL, fun, NULL);

    // 等待全部线程执行完成
    for (int i = 0;i < 2;i++) {
        pthread_join(tid[i], NULL);
    }

    printf("num的值是:%lld\n", num);
    return 0;
}

 两个线程使用同一个资源,出现资源竞争问题。

 锁(解决方式,实现同步)

如何避免竞态条件

上述程序如果想避免竞态条件,有下面两种解决方案:

  1. 避免多线程写入一个地址。
  2. 给资源加锁,使同一时间操作特定资源的线程只有一个。

方法1可以通过逻辑上组织业务逻辑实现,这里我们讲方法2。

想解决竞争问题,我们需要互斥锁——mutex。

常见的锁机制

锁主要用于互斥,即在同一时间只允许一个执行单元(进程或线程)访问共享资源。包括上面的互斥锁在内,常见的锁机制共有三种:

  1. 互斥锁(Mutex):保证同一时刻只有一个线程可以执行临界区的代码。
  2. 读写锁(Reader/Writer Locks):允许多个读者同时读共享数据,但写者的访问是互斥的。
  3. 自旋锁(Spinlocks):在获取锁之前,线程在循环中忙等待,适用于锁持有时间非常短的场景,一般是Linux内核使用。

互斥锁

pthread_mutex_t 是一个定义在头文件<pthreadtypes.h>中的联合体类型的别名,其声明如下。

typedef union
{
  struct __pthread_mutex_s __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

pthread_mutex_t用作线程之间的互斥锁。互斥锁是一种同步机制,用来控制对共享资源的访问。在任何时刻,最多只能有一个线程持有特定的互斥锁。如果一个线程试图获取一个已经被其他线程持有的锁,那么请求锁的线程将被阻塞,直到锁被释放。

用途

  • 保护共享数据,避免同时被多个线程访问导致的数据不一致问题。
  • 实现线程间的同步,确保线程之间对共享资源的访问按照预定的顺序进行。

操作

  • 初始化(pthread_mutex_init):创建互斥锁并初始化。
  • 锁定(pthread_mutex_lock):获取互斥锁。如果锁已经被其他线程持有,调用线程将阻塞。
  • 尝试锁定(pthread_mutex_trylock):尝试获取互斥锁。如果锁已被持有,立即返回而不是阻塞。
  • 解锁(pthread_mutex_unlock):释放互斥锁,使其可被其他线程获取。
  • 销毁(pthread_mutex_destroy):清理互斥锁资源。
#include <pthread.h>
/**
 * @brief 获取锁,如果此时锁被占则阻塞
 * 
 * @param mutex 锁
 * @return int 获取锁结果
 */
int pthread_mutex_lock(pthread_mutex_t *mutex);

/**
 * @brief 非阻塞式获取锁,如果锁此时被占则返回EBUSY
 * 
 * @param mutex 锁
 * @return int 获取锁结果
 */
int pthread_mutex_trylock(pthread_mutex_t *mutex);

/**
 * @brief 释放锁
 * 
 * @param mutex 锁
 * @return int 释放锁结果
 */
int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_lock

该函数用于锁定指定的互斥锁。如果互斥锁已经被其他线程锁定,调用此函数的线程将会被阻塞,直到互斥锁变为可用状态。这意味着如果另一个线程持有锁,当前线程将等待直到锁被释放。

成功时返回0;失败时返回错误码。

pthread_mutex_trylock

该函数尝试锁定指定的互斥锁。与pthread_mutex_lock不同,如果互斥锁已经被其他线程锁定,pthread_mutex_trylock不会阻塞调用线程,而是立即返回一个错误码(EBUSY)。

如果成功锁定互斥锁,则返回0;如果互斥锁已被其他线程锁定,返回EBUSY;其他错误情况返回不同的错误码。

pthread_mutex_unlock

该函数用于解锁指定的互斥锁。调用线程必须是当前持有互斥锁的线程;否则,解锁操作可能会失败。

成功时返回0;失败时返回错误码。

初始化互斥锁

PTHREAD_MUTEX_INITIALIZER是POSIX线程(Pthreads)库中定义的一个宏,用于静态初始化互斥锁(mutex)。这个宏为互斥锁提供了一个初始状态,使其准备好被锁定和解锁,而不需要在程序运行时显式调用初始化函数。

当我们使用PTHREAD_MUTEX_INITIALIZER初始化互斥锁时,实际上是将互斥锁设置为默认属性和未锁定状态。这种初始化方式适用于简单的同步问题,我们可以通过以下代码初始化互斥锁。

static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

案例(对上面那个资源竞争的代码稍作修改):

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* fun(void* arg) {
    for (int i = 0;i < 1000000;i++) { // 加100w
        // 获取锁
        pthread_mutex_lock(&mutex);
        num++;
        // 释放锁
        pthread_mutex_unlock(&mutex);

    }
}

读写锁

读操作:在读写锁的控制下,多个线程可以同时获得读锁。这些线程可以并发地读取共享资源,但它们的存在阻止了写锁的授予。

写操作:如果至少有一个读操作持有读锁,写操作就无法获得写锁。写操作将会阻塞,直到所有的读锁都被释放。

pthread_rwlock_t

typedef union
{
  struct __pthread_rwlock_arch_t __data;
  char __size[__SIZEOF_PTHREAD_RWLOCK_T];
  long int __align;
} pthread_rwlock_t;

 pthread_rwlock_init()

/**
 * @brief 为rwlock指向的读写锁分配所有需要的资源,并将锁初始化为未锁定状态。读写锁的属性由attr参数指定,如果attr为NULL,则使用默认属性。当锁的属性为默认时,可以通过宏PTHREAD_RWLOCK_INITIALIZER初始化,即
 * pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; 效果和调用当前方法并为attr传入NULL是一样的
 * 
 * @param rwlock 读写锁
 * @param attr 读写锁的属性
 * @return int 成功则返回0,否则返回错误码
 */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

pthread_rwlock_destroy()

#include <pthread.h>

/**
 * @brief 销毁rwlock指向的读写锁对象,并释放它使用的所有资源。当任何线程持有锁的时候销毁锁,或尝试销毁一个未初始化的锁,结果是未定义的。
 * 
 * @param rwlock 
 * @return int 
 */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

 pthread_rwlock_rdlock()​​​​​​​

/**
 * @brief 应用一个读锁到rwlock指向的读写锁上,并使调用线程获得读锁。如果写线程持有锁,调用线程无法获得读锁,它会阻塞直至获得锁。
 * 
 * @param rwlock 读写锁
 * @return int 成功返回0,失败返回错误码
 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

 ​​​​​​​pthread_rwlock_wrlock()

/**
 * @brief 应用一个写锁到rwlock指向的读写锁上,并使调用线程获得写锁。只要任意线程持有读写锁,则调用线程无法获得写锁,它将阻塞直至获得写锁。
 * 
 * @param rwlock 读写锁
 * @return int 成功返回0,失败返回错误码
 */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

 ​​​​​​​pthread_rwlock_unlock()

/**
 * @brief 释放调用线程锁持有的rwlock指向的读写锁。
 * 
 * @param rwlock 读写锁
 * @return int 成功返回0.失败返回错误码
 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

案例: 

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

pthread_rwlock_t rwlock;
int shared_data = 0;

void* lock_reader(void* args) {
    // 读写锁中的读是可以多个线程同时读取的
    // 获取读锁
    pthread_rwlock_rdlock(&rwlock);
    printf("%s读取到:%d\n", (char*)args, shared_data);
    pthread_rwlock_unlock(&rwlock);
    free(args);
}

void* lock_writer(void* args) {
    // 获取写锁
    pthread_rwlock_wrlock(&rwlock);
    sleep(1);
    shared_data++;
    printf("当前%s写入完毕,结果是%d\n", (char*)args, shared_data);
    // 释放写锁
    pthread_rwlock_unlock(&rwlock);
}

int main(int argc, char const* argv[])
{
    // 动态初始化创建锁
    pthread_rwlock_init(&rwlock,NULL);

    
    pthread_t writer1, writer2;
    pthread_t read[10];
    // 写线程
    pthread_create(&writer1, NULL, lock_writer, "写线程1");
    pthread_create(&writer2, NULL, lock_writer, "写线程2");

    sleep(3);

    // 读线程
    for (int i = 0;i < 10;i++) {
        char* s = (char*)malloc(20 * sizeof(char));
        sprintf(s, "读线程%d", i);
        pthread_create(&read[i], NULL, lock_reader, s);
    }

    // 主线程等待
    pthread_join(writer1, NULL);
    pthread_join(writer2, NULL);
    for (int i = 0;i < 10;i++) {
        pthread_join(read[i], NULL);
    }

    pthread_rwlock_destroy(&rwlock);
    return 0;
}

​​​​​​​

要注意的是,线程的执行顺序是由操作系统内核调度的,其运行规律并不简单地为“先创建先执行”。

写饥饿

 

多次运行后,我们发现,此时读操作总是连续执行的,且读操作休眠未结束时,写操作会被阻塞。与工作原理相符:① 读操作可以并发执行,相互之间不必争抢锁,多个读操作可以同时获得读锁;② 只要有一个线程持有读写锁,写操作就会被阻塞。我们在读操作中加了1s休眠,只要有一个读线程获得锁,在1s内写操作是无法执行的,其它读操作就可以有充足的时间执行,因此读操作就会连续发生,写操作必须等待所有读操作执行完毕方可获得读写锁执行写操作。这就是使用读写锁时存在的潜在问题:写饥饿。

解决方法

① 问题描述

读写锁的写饥饿问题(Writer Starvation)是指在使用读写锁时,写线程可能无限期地等待获取写锁,因为读线程持续地获取读锁而不断地推迟写线程的执行。这种情况通常在读操作远多于写操作时出现。

② 解决方案

Linux提供了可以修改的属性pthread_rwlockattr_t,默认情况下,属性中指定的策略为“读优先”,当写操作阻塞时,读线程依然可以获得读锁,从而在读操作并发较高时导致写饥饿问题。我们可以尝试将策略更改为“写优先”,当写操作阻塞时,读线程无法获取锁,避免了写线程持有锁的时间持续延长,使得写线程获取锁的等待时间显著降低,从而避免写饥饿问题。

 pthread_rwlockattr_t

typedef union
{
  char __size[__SIZEOF_PTHREAD_RWLOCKATTR_T];
  long int __align;
} pthread_rwlockattr_t;

pthread_rwlockattr_init

#include <pthread.h>

/**
 * @brief 用所有属性的默认值初始化attr指向的属性对象
 * 
 * @param attr 读写锁属性对象指针
 * @return int 成功返回0,失败返回错误码
 */
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

pthread_rwlockattr_destroy

#include <pthread.h>

/**
 * @brief 销毁读写锁属性对象
 * 
 * @param attr 读写锁属性对象指针
 * @return int 成功返回0,失败返回错误码
 */
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

pthread_rwlockattr_setkind_np

#include <pthread.h>

/**
 * @brief 将attr指向的属性对象中的"锁类型"属性设置为pref规定的值
 * 
 * @param attr 读写锁属性对象指针
 * @param pref 希望设置的锁类型,可以被设置为以下三种取值的其中一种
 * PTHREAD_RWLOCK_PREFER_READER_NP: 默认值,读线程拥有更高优先级。当存在阻塞的写线程时,读线程仍然可以获得读写锁。只要不断有新的读线程,写线程将一直保持"饥饿"。
 * PTHREAD_RWLOCK_PREFER_WRITER_NP: 写线程拥有更高优先级。这一选项被glibc忽略。
 * PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: 写线程拥有更高优先级,在当前系统环境下,它是有效的,将锁类型设置为该值以避免写饥饿。
 * @return int 成功返回0,失败返回非零的错误码
 */
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);

 案例:

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

pthread_rwlock_t rwlock;
int shared_data = 0;

void* lock_reader(void* args) {
    // 读写锁中的读是可以多个线程同时读取的
    // 获取读锁
    printf("%s开始获取读锁\n",(char*)args);
    pthread_rwlock_rdlock(&rwlock);
    printf("%s读取到:%d\n", (char*)args, shared_data);
    sleep(1);
    pthread_rwlock_unlock(&rwlock);
    free(args);
}

void* lock_writer(void* args) {
    // 获取写锁
    printf("%s开始获取写锁\n",(char*)args);
    pthread_rwlock_wrlock(&rwlock);
    shared_data++;
    printf("当前%s写入完毕,结果是%d\n", (char*)args, shared_data);
    // 释放写锁
    pthread_rwlock_unlock(&rwlock);
}

int main(int argc, char const* argv[])
{
    // 创建写锁属性对象
    pthread_rwlockattr_t attr;
    pthread_rwlockattr_init(&attr);
    // 设置写线程优先级
    pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
    // 动态初始化创建锁
    pthread_rwlock_init(&rwlock,&attr);

    
    pthread_t writer1, writer2;
    pthread_t read[10];
    // 写线程
    pthread_create(&writer1, NULL, lock_writer, "写线程1");
    

    // 读线程
    for (int i = 0;i < 10;i++) {
        char* s = (char*)malloc(20 * sizeof(char));
        sprintf(s, "读线程%d", i);
        pthread_create(&read[i], NULL, lock_reader, s);
        if (i == 4) {
            pthread_create(&writer2, NULL, lock_writer, "写线程2");
        }
    }

    // 主线程等待
    pthread_join(writer1, NULL);
    pthread_join(writer2, NULL);
    for (int i = 0;i < 10;i++) {
        pthread_join(read[i], NULL);
    }

    pthread_rwlock_destroy(&rwlock);
    pthread_rwlockattr_destroy(&attr);
    return 0;
}

可以看到,写线程是先于读线程的。 不会像前面那样,出现写饥饿问题。

自旋锁

(我们写的用户锁一般是要尽量避免空转的,而对于自旋锁是属于操作系统内核的锁,不需要像用户锁那样去避免空转) 

在Linux内核中,自旋锁是一种用于多处理器系统中的低级同步机制,主要用于保护非常短的代码段或数据结构,以避免多个处理器同时访问共享资源。自旋锁相对于其他锁的优点是它们在锁被占用时会持续检查锁的状态(即“自旋”),而不是让线程进入休眠。这使得自旋锁在等待时间非常短的情况下非常有效,因为它避免了线程上下文切换的开销。

自旋锁主要用于内核模块或驱动程序中,避免上下文切换的开销。不能在用户空间使用。

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

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

相关文章

vue2 web 多标签输入框 elinput是否当前焦点

又来分享一点点工作积累及解决方案 产品中需要用户输入一些文字后按下回车键生成标签来显示在页面上&#xff0c;经过尝试与改造完成如下&#xff1a; <template><div class"tags-view" click"beginInput"><el-tag :key"index" …

Python学习(十)IO编程(文件读写、StringIO和BytesIO、操作文件和目录、序列化)

目录 一、什么是IO编程&#xff1f;二、文件读写1&#xff09;读文件2&#xff09;file-like Object3&#xff09;二进制文件4&#xff09;字符编码5&#xff09;写文件 三、StringIO 和 BytesIO1&#xff09;StringIO2&#xff09;BytesIO 四、操作文件和目录1&#xff09;操作…

5、docker-compose和docker-harbor

安装部署docker-compose 自动编排工具&#xff0c;可以根据dockerfile自动化的部署docker容器。是yaml文件格式&#xff0c;注意缩进。 1、安装docker-compose 2、配置compose配置文件docker-compose.yml 3、运行docker-compose.yml -f&#xff1a;指定文件&#xff0c;up&…

JS宏进阶: 工厂函数与构造函数

一、构造函数 在JavaScript中&#xff0c;构造函数是一种用于创建和初始化对象的特殊函数。构造函数的名字通常以大写字母开头&#xff0c;以区分于普通函数。通过new关键字调用构造函数&#xff0c;可以创建一个新的实例对象&#xff0c;并自动执行构造函数内部的代码来初始化…

uniapp 微信小程序 editor 富文本编辑器

<view class"inp boxsizing"><view class"contentBox"><!-- 富文本编辑器 --><view classwrapper><view classtoolbar tap"format"><view :class"formats.bold ? ql-active : " class"iconfon…

Python根据图片生成学生excel成绩表

学习笔记&#xff1a; 上完整代码 import os import re from openpyxl import Workbook, load_workbook from openpyxl.drawing.image import Image as ExcelImage from PIL import Image as PilImage# 定义图片路径和Excel文件路径 image_dir ./resources/stupics # 图片所…

在VMwareFusion中使用Ubuntu

在VMwareFusion使用Ubuntu 在VMwareFusion使用Ubuntu背景在VMwareFusion虚拟机里使用Ubuntu1、集成桌面工具2、主机和虚拟机之间共享剪贴板内容3、设置root用户密码4、设置静态ip4.1、静态ip和动态ip的区别4.2、查看当前ip4.2、linux网络配置文件所在位置4.3、基于ubuntu22.04.…

农业农村大数据应用场景|珈和科技“数字乡村一张图”解决方案

近年来&#xff0c;珈和科技持续深耕农业领域&#xff0c;聚焦时空数据服务智慧农业。 珈和利用遥感大数据、云计算、移动互联网、物联网、人工智能等先进技术&#xff0c;搭建“天空地一体化”监测体系&#xff0c;并创新建设了150的全球领先算法模型&#xff0c;广泛应用于高…

python 利用 ddddocr包 ocr识别图片码

ddddocr 是一个轻量级的 OCR&#xff08;光学字符识别&#xff09;库&#xff0c;适用于识别图片中的文字&#xff0c;包括验证码等图像文本。要使用 ddddocr 进行图片验证码的识别&#xff0c;可以按照以下步骤进行&#xff1a; 1. 安装 ddddocr 包 首先&#xff0c;你需要安…

【论文阅读】基于空间相关性与Stacking集成学习的风电功率预测方法

文章目录 摘要0. 引言1. 空间相关性分析2. 风电功率预测模型2.1 Stacking 集成策略2.2 基学习器2.2.1 基于机器学习算法的基学习器2.2.2 基于神经网络的基学习器2.2.3 基于粒子群优化算法的超参数优化 2.3 元学习器2.4 基于空间相关性与Stacking集成学习的风电功率预测方法 3 算…

在.NET用C#将Word文档转换为HTML格式

将Word文档转换为HTML格式尤其具有显著的优势&#xff0c;它不仅能够确保文档内容在多种设备和平台上保持一致灵活的显示&#xff0c;还便于通过网络进行传播和集成到各种Web应用中。随着越来越多的企业和开发者寻求更灵活、更具兼容性的文件处理方式&#xff0c;.NET框架下的C…

EasyExcel的应用

一、简单使用 引入依赖&#xff1a; 这里我们可以使用最新的4.0.2版本&#xff0c;也可以选择之前的稳定版本&#xff0c;3.1.x以后的版本API大致相同&#xff0c;新的版本也会向前兼容&#xff08;3.1.x之前的版本&#xff0c;部分API可能在高版本被废弃&#xff09;&…

【git】如何删除本地分支和远程分支?

1.如何在 Git 中删除本地分支 本地分支是您本地机器上的分支&#xff0c;不会影响任何远程分支。 &#xff08;1&#xff09;在 Git 中删除本地分支 git branch -d local_branch_name git branch 是在本地删除分支的命令。-d是一个标志&#xff0c;是命令的一个选项&#x…

wps数据分析000002

目录 一、快速定位技巧 二、快速选中技巧 全选 选中部分区域 选中部分区域&#xff08;升级版&#xff09; 三、快速移动技巧 四、快速录入技巧 五、总结 一、快速定位技巧 ctrl→&#xff08;上下左右&#xff09;快速定位光标对准单元格的上下部分双击名称单元格中…

Java算法 二叉树入门 力扣简单题相同的树 翻转二叉树 判断对称二叉树 递归求二叉树的层数

目录 模版 先序遍历 中序遍历 后序遍历 力扣原题 相同的二叉树 力扣原题 翻转二叉树 遍历树的层数 题目 静态变量 核心逻辑 模版 // 二叉树public static class Node{public int value;public Node left;public Node right;public Node(int v) {valuev;}} 先序遍历 …

【Mysql进阶知识】Mysql 程序的介绍、选项在命令行配置文件的使用、选项在配置文件中的语法

目录 一、程序介绍 二、mysqld--mysql服务器介绍 三、mysql - MySQL 命令行客户端 3.1 客户端介绍 3.2 mysql 客户端选项 指定选项的方式 mysql 客户端命令常用选项 在命令行中使用选项 选项(配置)文件 使用方法 选项文件位置及加载顺序 选项文件语法 使用举例&am…

ESP32云开发二( http + led + lcd)

文章目录 前言先上效果图platformio.iniwokwi.tomldiagram.json源代码编译编译成功上传云端完结撒花⭐⭐⭐⭐⭐ 前言 阅读此篇前建议先看 此片熟悉下wokwi https://blog.csdn.net/qq_20330595/article/details/144289986 先上效果图 Column 1Column 2 platformio.ini wokwi…

医疗集群系统中基于超融合数据库架构的应用与前景探析

一、引言 1.1 研究背景与意义 随着医疗信息化的飞速发展,医疗数据呈爆炸式增长。从日常诊疗记录、患者病历,到各类医疗影像、检查检验数据等,海量信息不断涌现。据统计,医疗数据的年增长率高达 30% 以上 ,2025 年,全球医疗数据量将达到 2314 艾字节(EB)。如此庞大的数…

Flask学习入门笔记

Flask学习入门笔记 前言1. 安装Flask2. 创建一个简单的Flask应用3. 路由与视图函数3.1 基本路由3.2 动态路由3.3 HTTP方法 4. 请求与响应4.1 获取请求数据4.2 返回响应 5. 模板渲染5.1 基本模板渲染5.2 模板继承 6. 静态文件6.1 静态文件的目录结构6.2 在模板中引用静态文件6.2…

Java File、IO流、字节输入流 、字节输出流 、字符输入流 、字符输入流 、缓冲流、转换流、打印流、数据流、序列化流、IO框架

一. File File是Java.io.包下的类&#xff0c;File类的对象用于代表当前操作系统的文件/文件夹 File类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据。 1. 创建对象 构造器说明public File(String pathname)根据文件路径创建文件对象public File(String pare…