JUC之Java内置锁的核心原理

news2024/10/1 8:17:54

文章目录

      • JUC之Java内置锁的核心原理
        • Java对象结构
          • 对象头
          • 对象体
          • 对齐字节
        • Mark Word的结构信息
          • 64位Mark Word的构成
        • 偏向锁
          • 偏向锁的设置
          • 偏向锁的重偏向
          • 偏向锁的撤销
          • 偏向锁的膨胀
        • 轻量级锁
          • 执行过程
          • 轻量级锁的分类
            • 普通自旋锁
            • 自适应自旋锁
        • 重量级锁
        • 偏向锁、轻量级锁与重量级锁的对比

JUC之Java内置锁的核心原理

​ Java内置锁是一个互斥锁,这就意味着最多只有一个线程能够获 得该锁,当线程B尝试去获得线程A持有的内置锁时,线程B必须等待或 者阻塞,直到线程A释放这个锁,如果线程A不释放这个锁,那么线程B 将永远等待下去。

​ Java中每个对象都可以用作锁,这些锁称为内置锁。线程进入同 步代码块或方法时会自动获得该锁,在退出同步代码块或方法时会释 放该锁。获得内置锁的唯一途径就是进入这个锁保护的同步代码块或 方法。

Java对象结构

Java对象(Object实例)结构包括三部分:对象头、对象体和对齐字节。

结构如下:

在这里插入图片描述

对象头

​ 对象头包括三个字段,第一个字段叫作Mark Word(标记字),用于存储自身运行时的数据,例如GC标志位、哈希码、锁状态信息等。

​ 第二个字段叫作Class Pointer(类对象指针),用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例。

​ 第三个字段叫作Array Length(数组长度)。如果对象是一个Java数组,那这个字段必须要有,用于记录数组长度的数据;如果对象不是一个Java数组,那么此字段不存在,所以这是一个可选字段。

对象体

​ 对象体包含对象的实例变量(成员变量),用于成员属性值,包括父类的成员属性值。这部分内存按4字节对齐。对象体是对象的主体部分,占用的内存空间大小取决于对象的属性数量和类型。

对齐字节

​ 对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存 字节数为8的倍数HotSpot VM的内存管理要求对象起始地址必须是8字 节的整数倍。对象头本身是8的倍数,当对象的实例变量数据不是8的 倍数时,便需要填充数据来保证8字节的对齐。

​ 对齐字节并不是必然存在的,也没有特别的含义,它仅仅起 着占位符的作用。当对象实例数据部分没有对齐(8字节的整数倍) 时,就需要通过对齐填充来补全。

Mark Word的结构信息

​ Java内置锁涉及很多重要信息,这些都存放在对象头的Mark Word字段中。Mark Word不会受到Oop指针压缩选项的影响。Java内置锁的状态总共有4种,级别由低到高依次为:无锁、偏向锁、轻量级锁和重量级锁。在JDK1.6之前只有重量级锁,之后才引入偏向锁和轻量级锁。4种锁状态会随着竞争的情况逐渐升级,而且不可逆,即只能进行锁升级,不会发生锁降级。

不同锁状态下32位Mark Work的结构信息:

在这里插入图片描述

不同锁状态下64位Mark Work的结构信息:

在这里插入图片描述

64位Mark Word的构成
  1. lock:锁状态标记位,占两个二进制位。该标记的 值不同,整个Mark Word表示的含义就不同。
  2. biased_lock:对象是否启用偏向锁标记,只占1个二进制 位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
  3. age:4位的Java对象分代年龄。在GC中,对象在Survivor区 复制一次,年龄就增加1。当对象达到设定的阈值时,将会晋升到老年 代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由 于age只有4位,因此最大值为15,这就是-XX:MaxTenuringThreshold 选项最大值为15的原因。
  4. identity_hashcode:31位的对象标识HashCode(哈希码) 采用延迟加载技术,当调用Object.hashCode()方法或者 System.identityHashCode()方法计算对象的HashCode后,其结果将被 写到该对象头中。当对象被锁定时,该值会移动到Monitor(监视器) 中。
  5. thread:54位的线程ID值为持有偏向锁的线程ID。
  6. epoch:偏向时间戳。
  7. ptr_to_lock_record:占62位,在轻量级锁的状态下指向栈帧中锁记录的指针。
  8. ptr_to_heavyweight_monitor:占62位,在重量级锁的状态下指向对象监视器的指针。

32位的Mark Word与64位的Mark Word结构相似

偏向锁

​ 如果一个同步块(或方法)没有多个线程竞争, 而且总是由同一个线程多次重入获取锁,如果每次还有阻塞线程,唤 醒CPU从用户态转为核心态,那么对于CPU是一种资源的浪费,为了解 决这类问题,就引入了偏向锁的概念。偏向锁主要解决无竞争下的锁性能问题,所谓的偏向就是偏心, 即锁会偏向于当前已经占有锁的线程。

​ 原理:如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态,此时Mark Word的结构变为偏向锁结构, 锁对象的锁标志位(lock)被改为01,偏向标志位(biased_lock)被改为1,然后线程的ID记录在锁对象的Mark Word中(使用CAS操作完 成)。以后该线程获取锁时判断一下线程ID和标志位,就可以直接进 入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。

偏向锁的设置

​ 如果开启了偏向锁(默认开启),那么对象创建后,markword值为0x05即最后3位为101,这时它的thread、epoch、age都为0;如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后3位位001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值。使用-XX:-UseBiasedLocking可以禁用偏向锁。

​ 偏向锁是默认延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数-XX:BiasedLockingStartupDelay=0 来禁用延迟。

偏向锁的重偏向

​ 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T 1 的对象仍有机会重新偏向T 2 ,重偏向会重置对象的 Thread ID。当撤销偏向锁阈值超过20次后,(从第二十次开始)jvm之后在给这些对象加锁时重新加偏向锁至新的线程。

偏向锁的撤销

偏向锁的撤销过程:

  1. 在一个安全点停止拥有锁的线程。
  2. 遍历线程的栈帧,检查是否存在锁记录。如果存在锁记录, 就需要清空锁记录,使其变成无锁状态,并修复锁记录指向的Mark Word,清除其线程ID。
  3. 将当前锁升级成轻量级锁。
  4. 唤醒当前线程。

​ 所以,如果某些临界区存在两个及两个以上的线程竞争,那么偏 向锁反而会降低性能。在这种情况下,可以在启动JVM时就把偏向锁的 默认功能关闭。

撤销偏向锁的情况:

  1. 多个线程竞争偏向锁。
  2. 调用偏向锁对象的hashcode()方法或者 System.identityHashCode()方法计算对象的HashCode之后,将哈希码 放置到Mark Word中,内置锁变成无锁状态,偏向锁将被撤销。
  3. 调用 wait/notify,这个只有重量级锁才有

当撤销偏向锁阈值超过40次后,(从第四十次开始)jvm会将整个类的所有对象都变为不可偏向的,新建的对象也会是不可偏向的。

偏向锁的膨胀

​ 如果偏向锁被占据,一旦有第二个线程争抢这个对象,因为偏向 锁不会主动释放,所以第二个线程可以看到内置锁偏向状态,这时表 明在这个对象锁上已经存在竞争了。JVM检查原来持有该对象锁的占有 线程是否依然存活,如果挂了,就可以将对象变为无锁状态,然后进 行重新偏向,偏向为抢锁线程。

​ 如果JVM检查到原来的线程依然存活,就进一步检查占有线程的调 用堆栈是否通过锁记录持有偏向锁。如果存在锁记录,就表明原来的 线程还在使用偏向锁,发生锁竞争,撤销原来的偏向锁,将偏向锁膨胀(INFLATING)为轻量级锁。

轻量级锁

使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁的本意是为了减少多线程进入操作系统底层的互斥锁 (Mutex Lock)的概率,并不是要替代操作系统互斥锁。所以,在争 用激烈的场景下,轻量级锁会膨胀为基于操作系统内核互斥锁实现的 重量级锁。

执行过程

(1)在抢锁线程进入临界区之前,如果内置锁 (临界区的同步对象)没有被锁定,JVM首先将在抢锁线程的栈帧中建 立一个锁记录(Lock Record),用于存储对象目前Mark Word的拷 贝,这时的线程堆栈与内置锁对象头大致如图所示,

在这里插入图片描述

(2)然后抢锁线程将使用CAS自旋操作,尝试将内置锁对象头的Mark Word的ptr_to_lock_record(锁记录指针)更新为抢锁线程栈帧中锁 记录的地址,如果这个更新执行成功了,这个线程就拥有了这个对象锁。然后JVM将Mark Word中的lock标记位改为00(轻量级锁标志), 即表示该对象处于轻量级锁状态。

(3)抢锁成功之后,JVM会将Mark Word 中原来的锁对象信息(如哈希码等)保存在抢锁线程锁记录的 Displaced Mark Word(可以理解为放错地方的Mark Word)字段中, 再将抢锁线程中锁记录的owner指针指向锁对象。

在轻量级锁抢占成功之后,锁记录和对象头的状态如图所示,

在这里插入图片描述

如果cas失败,有两种情况:

  1. 如果是其他线程已经持有了该Object的轻量级锁,这时表明有就竞争,进入锁膨胀过程
  2. 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

当退出 synchronized 代码块(解锁时)如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一。

当退出 synchronized 代码块(解锁时)锁记录的值不为null,这时使用 cas 将 Mark Word 的值恢复给对象头,成功则解锁成功,失败说明轻量级锁已经进行了锁膨胀或者已经升级为重量级锁,进入重量级锁的解锁流程。

轻量级锁的分类

轻量级锁主要有两种:普通自旋锁和自适应自旋锁。

普通自旋锁

​ 普通自旋锁指当有线程来竞争锁时,抢锁线程会在原 地循环等待,而不是被阻塞,直到那个占有锁的线程释放锁之后,这 个抢锁线程才可以获得锁。

​ 锁在原地循环等待的时候是会消耗CPU的,就相当于在执行一个 什么也不干的空循环。所以轻量级锁适用于临界区代码耗时很短的场 景,这样线程在原地等待很短的时间就能够获得锁了。默认情况下,自旋的次数为10次,用户可以通过XX:PreBlockSpin选项来进行更改。

自适应自旋锁

​ 自适应自旋锁就是等待线程空循环的自旋次数并非是固定 的,而是会动态地根据实际情况来改变自旋等待的次数,自旋次数由 前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

  • 如果抢锁线程在同一个锁对象上之前成功获得过锁,JVM就 会认为这次自旋很有可能再次成功,因此允许自旋等待持续相对更长 的时间。
  • 如果对于某个锁,抢锁线程很少成功获得过,那么JVM将可 能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源。

JDK 1.6的轻量级锁使用的是普通自旋锁,且需要使用XX:+UseSpinning选项手工开启。JDK 1.7后,轻量级锁使用自适应自旋 锁,JVM启动时自动开启,且自旋时间由JVM自动控制。

重量级锁

​ JVM中每个对象都会有一个监视器,监视器和对象一起创建、销 毁。监视器相当于一个用来监视这些线程进入的特殊房间,其义务是 保证(同一时间)只有一个线程可以访问被保护的临界区代码块。本质上,监视器是一种同步工具,也可以说是一种同步机制,监听器主要有两个特点。

  • 同步。监视器所保护的临界区代码是互斥地执行的。一个监 视器是一个运行许可,任一线程进入临界区代码都需要获得这个许 可,离开时把许可归还。
  • 协作。监视器提供Signal机制,允许正持有许可的线程暂时 放弃许可进入阻塞等待状态,等待其他线程发送Signal去唤醒;其他 拥有许可的线程可以发送Signal,唤醒正在阻塞等待的线程,让它可 以重新获得许可并启动执行。

在Hotspot虚拟机中,监视器是由C++类ObjectMonitor实现的,ObjectMonitor的Owner(_owner)、WaitSet(_WaitSet)、 Cxq(_cxq)、EntryList(_EntryList)这几个属性比较关键。ObjectMonitor的WaitSet、Cxq、EntryList这三个队列存放抢夺重量 级锁的线程,而ObjectMonitor的Owner所指向的线程即为获得锁的线 程。

  • Cxq:竞争队列(Contention Queue),所有请求锁的线程 首先被放在这个竞争队列中。
  • EntryList:Cxq中那些有资格成为候选资源的线程被移动到 EntryList中。
  • WaitSet:某个拥有ObjectMonitor的线程在调用 Object.wait()方法之后将被阻塞,然后该线程将被放置在WaitSet链 表中。

ObjectMonitor的内部抢锁过程如图所示:

在这里插入图片描述

(1) Cxq

​ Cxq并不是一个真正的队列,而是一个由Node及其next指针逻辑构成的虚拟队列。每 次新加入Node会在Cxq的队头进行,通过CAS改变第一个节点的指针为 新增节点,同时设置新增节点的next指向后续节点;从Cxq取得元素 时,会从队尾获取。Cxq结构是一个无锁结构。

​ 在线程进入Cxq前,抢锁线程会先尝试通过CAS自旋获取锁,如果 获取不到,就进入Cxq队列,这明显对于已经进入Cxq队列的线程是不 公平的。因此,synchronized同步块所使用的重量级锁是不公平锁。

(2) EntryList

​ EntryList与Cxq在逻辑上都属于等待队列。Cxq会被线程并发访 问,为了降低对Cxq队尾的争用,而建立EntryList。在Owner线程释放 锁时,JVM会从Cxq中迁移线程到EntryList,并会指定EntryList中的 某个线程(一般为Head)为OnDeck Thread(Ready Thread)。 EntryList中的线程作为候选竞争线程而存在。

(3) OnDeck Thread与Owner Thread

​ JVM不直接把锁传递给Owner Thread,而是把锁竞争的权利交给 OnDeck Thread,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平 性,但是能极大地提升系统的吞吐量,在JVM中,也把这种选择行为称 为“竞争切换”。

​ OnDeck Thread获取到锁资源后会变为Owner Thread。无法获得锁 的OnDeck Thread则会依然留在EntryList中,考虑到公平性,OnDeck Thread在EntryList中的位置不发生变化(依然在队头)。

​ 在OnDeck Thread成为Owner的过程中,还有一个不公平的事情, 就是后来的新抢锁线程可能直接通过CAS自旋成为Owner而抢到锁。

(4) WaitSet

​ 如果Owner线程被Object.wait()方法阻塞,就转移到WaitSet队列 中,直到某个时刻通过Object.notify()或者Object.notifyAll()唤 醒,该线程就会重新进入EntryList中。

偏向锁、轻量级锁与重量级锁的对比

synchronized的执行过程:

  1. 线程抢锁时,JVM首先检测内置锁对象Mark Word中的 biased_lock(偏向锁标识)是否设置成1,lock(锁标志位)是否为 01,如果都满足,确认内置锁对象为可偏向状态。
  2. 在内置锁对象确认为可偏向状态之后,JVM检查Mark Word中 的线程ID是否为抢锁线程ID,如果是,就表示抢锁线程处于偏向锁状 态,抢锁线程快速获得锁,开始执行临界区代码。
  3. 如果Mark Word中的线程ID并未指向抢锁线程,就通过CAS操 作竞争锁。如果竞争成功,就将Mark Word中的线程ID设置为抢锁线 程,偏向标志位设置为1,锁标志位设置为01,然后执行临界区代码, 此时内置锁对象处于偏向锁状态。
  4. 如果CAS操作竞争失败,就说明发生了竞争,撤销偏向锁, 进而升级为轻量级锁。
  5. JVM使用CAS将锁对象的Mark Word替换为抢锁线程的锁记录 指针,如果成功,抢锁线程就获得锁。如果替换失败,就表示其他线 程竞争锁,JVM尝试使用CAS自旋替换抢锁线程的锁记录指针,如果自 旋成功(抢锁成功),那么锁对象依然处于轻量级锁状态。
  6. 如果JVM的CAS替换锁记录指针自旋失败,轻量级锁就膨胀为 重量级锁,后面等待锁的线程也要进入阻塞状态。

总体来说,偏向锁是在没有发生锁争用的情况下使用的;一旦有 了第二个线程争用锁,偏向锁就会升级为轻量级锁;如果锁争用很激 烈,轻量级锁的CAS自旋到达阈值后,轻量级锁就会升级为重量级锁。

三种内置锁的对比如图:

在这里插入图片描述

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

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

相关文章

探秘C语言经典题目:如何求解整数二进制中1的个数

本篇博客会讲解一道经典的题目:求一个整数二进制中1的个数。阅读本篇博客前,需要你对C语言如何进行二进制位操作有一定的了解,如果还不太了解的话,可以阅读一下我的这篇博客。 我们假设有一个int类型的整数n,我们知道…

12. Transformer(上)

P32 Transformer(上) 视频链接 P32 Transformer(上) Seq2seq应用: Seq2seq结构:

武忠祥老师每日一题||定积分基础训练(二)

仍是上一节中提到的基本思想 武忠祥老师每日一题||定积分基础训练(一) 在这个题中,M和N可以利用奇偶性判断。 如下: 从上可知, M ∫ − π 2 π 2 1 d x M\int_{-\frac{\pi}{2}}^{\frac{\pi}{2}}1\,{\rm d}x M∫−…

The 1st Universal Cup Stage 13: Iberia, Apr 22-23, 2023 题解

D. XOR Determinant You are given two arrays b and c of length n, consisting of non-negative integers. Construct n n matrix A as Aij bi ⊕ cj . Find the determinant of A modulo 998 244 353 考虑 A i j ∑ k b i , k c j , k p A_{ij}\sum_k b_{i,k}{c_{j,k}…

基于GWO灰狼优化算法的城市路径优化问题GWO-TSP(MATLAB程序)

资源地址: 基于GWO灰狼优化算法的城市路径优化问题GWO-TSP(MATLAB程序)资源-CSDN文库 主要内容: 主要采用灰狼优化算法对城市间的路径进行规划。城市分布图如图所示。 部分代码: % 产生问题模型 model CreateModel(Oliver30.…

p70 内网安全-域横向内网漫游 Socks 代理隧道技术(NPS、FRP、CFS 三层内网漫游)

数据来源 本文仅用于信息安全学习,请遵守相关法律法规,严禁用于非法途径。若观众因此作出任何危害网络安全的行为,后果自负,与本人无关。 ​ 必要基础知识点: 内外网简单知识内网 1 和内网 2 通信问题正向反向协议通…

linux用户管理指令

这里写自定义目录标题 一 增加新用户及密码二 切换用户三 userdel 删除用户四 查看用户登录信息五 让普通用户成为管理员1. 修改环境配置文件2.设置用户和密码 六 查看创建哪些用户 一 增加新用户及密码 useradd:加用户名 passwd:加用户密码 [rootlocalhost ~]# u…

HBASE整理

HBASE整理 一、HBASE由来 思考: HDFS主要适用于什么场景呢? 具有高的吞吐量 适合于批量数据的处理操作思考: 如果想在HDFS上, 直接读取HDFS上某一个文件中某一行数据, 请问是否可以办到呢? 或者说, 我们想直接修改HDFS上某一个文件中某一行数据,请问是否可以办到呢?HDFS并…

【Python】芜湖市空气质量指数可视化(散点图、分类散点图、单变量分布图、线性回归拟合图、相关性热力图)

【Python】芜湖市空气质量指数可视化 本文仅供学习参考,如有错误,还请指正! 一、简介 空气质量指数(Air Quality Index,AQI )简而言之就是能够对空气质量进行定量描述的数据。空气质量(Air Quality )的好坏…

《SQLi-Labs》02. Less 6~10

sqli Less-6知识点题解 Less-7题解 Less-8题解 Less-9知识点题解 Less-10题解 sqli。开启新坑。 Less-6 知识点 布尔盲注。与 Less-5 基本相同。这里只简略写大致步骤。 length() 函数:返回字符串所占的字节数。ascii() 函数:返回字符串最左字符的ASC…

OpenGL开发必过的坎------开发环境的准备(Windows10)

前言 图形编程一直以来都是计算机科学中最具挑战性的主题之一。随着限制VR技术的兴起,越来越多的公司开始涉足VR领域。目前来看使用最多的是Unity3d来开发,但是像浏览器,将2D应用3D化(把2D的应用界面投到一个3D的场景中&#xff…

FilmConvert Nitrate for Mac(fcpx/胶片模拟调色Pr/AE插件)

FilmConvert Nitrate是一款针对视频后期处理的插件,可用于颜色校正和外观看调整。它提供了各种预设,以方便用户足够快速地修改视频的外观,并还包含一个自定义工具集,以方便用户可以调整多个参数来达到他们所需要的效果。 该插件支…

SpringBoot + Druid DataSource 实现监控 MySQL 性能

1 添加依赖 <properties><java.version>1.8</java.version><alibabaDruidStarter.version>1.2.11</alibabaDruidStarter.version> </properties><dependency><groupId>com.alibaba</groupId><artifactId>druid-s…

Python基础合集 练习24 (程序调试)

assert expression[,arguments] expression条件表达式语句,如果表达式的值为真,则程序会继续执行下去,如果值为假则程序抛出Assertionerror错误,并输出指定的参数内容 arguments可选参数 if not expression: raise AssertionError(argument) def num_ca(): book int(inpu…

Rust 一门赋予每个人构建可靠且高效软件能力的语言

目录 Rust 安装 尝试 hello, world 编译 链接出错 开启 Rust 之旅 官方教程 《Rust 程序设计语言》 《通过例子学 Rust》 核心文档 标准库 版本指南 CARGO 手册 RUSTDOC 手册 RUSTC 手册 编译错误索引表 非官方翻译教程 Rust 程序设计语言 简体中文版 通…

543. 二叉树的直径【71】

难度等级&#xff1a;容易 上一篇算法&#xff1a; 199. 二叉树的右视图【111】 力扣此题地址&#xff1a; 543. 二叉树的直径 - 力扣&#xff08;Leetcode&#xff09; 1.题目&#xff1a;543. 二叉树的直径 给定一棵二叉树&#xff0c;你需要计算它的直径长度。一棵二叉树的…

玩一玩 Ubuntu 下的 VSCode 编程

一&#xff1a;背景 1. 讲故事 今天是五一的最后一天&#xff0c;想着长期都在 Windows 平台上做开发&#xff0c;准备今天换到 Ubuntu 系统上体验下&#xff0c;主要是想学习下 AT&T 风格的汇编&#xff0c;这里 Visual Studio 肯定是装不了了&#xff0c;还得上 VSCode…

Spring:依赖注入的方式(setter注入、构造器注入、自动装配、集合注入)

依赖注入的方式有setter注入、构造器注入、自动装配、集合注入 首先&#xff0c;Maven项目pom.xml依赖包如下&#xff1a; pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:…

Servlet中转发和重定向的区别

什么是转发&#xff0c;重定向&#xff1f; 转发和重定向都是用于在服务器和浏览器之间进行页面跳转的方式。 转发是在服务器内部进行的&#xff0c;当一个Servlet接收到请求后&#xff0c;它可以将请求转发给另一个Servlet或JSP页面来处理请求&#xff0c;但是浏览器不知道这…

C语言-学习之路-04

C语言-学习之路-04 数组与字符串一维数组一维数组的定义和使用一维数组的初始化数组名一维数组练习 二维数组二维数组的定义和使用二维数组的初始化数组名 字符数组与字符串字符串的输入输出随机数字符串处理函数 数组与字符串 数组&#xff1a;为了方便处理数据把具有相同类型…