当前位置: 首页 > news >正文

java基础面试题(3)

Java 核心知识点整理(常用类、集合、IO 流)

一、常用类(第九章)

1. 基本数据类型与包装类(以 int 和 Integer 为例)

对比维度基本数据类型(int)包装类(Integer)
用途仅用于定义常量、局部变量;不能用于泛型 ;方法参数、对象成员变量中极少使用可用于泛型;方法参数、对象成员变量中常用;提供丰富工具方法(如类型转换、常量定义)
存储方式局部变量存于 ;成员变量存于 (随对象)引用类型,实例对象存于 ,栈中存储对象引用地址
占用空间占用空间极小(固定 4 字节)占用空间更大(包含对象头、数据等额外信息)
默认值0null
特殊特性内部自带缓冲区:通过 Integer.valueOf(int)创建 -128~127范围内的对象时,复用缓存对象,不新建实例

2. 包装类的设计意义与拆装箱

(1)设计原因

基本数据类型不具备面向对象特性,存在实际开发限制:

  • 集合(如 List、Set)仅能存储引用类型,无法直接存储基本数据类型
  • 泛型仅支持引用类型,不支持基本数据类型
    因此为每个基本数据类型提供对应的包装类,实现 “对象化” 处理。
(2)拆装箱概念
  • 装箱 :基本数据类型 → 包装类,调用包装类的 valueOf()方法(如 Integer.valueOf(10)
  • 拆箱 :包装类 → 基本数据类型,调用包装类的 xxxValue()方法(如 integer.intValue()
(3)自动拆装箱(JDK5+)

JVM 自动完成拆装箱操作,无需手动调用方法(如 Integer i = 10; int num = i;),但 频繁拆装箱会严重影响系统性能 ,需避免不必要操作。

3. String、StringBuffer、StringBuilder 的区别与联系

特性StringStringBufferStringBuilder
可变性不可变 (字符序列创建后无法修改,修改时生成新对象)可变 (字符序列可动态修改,不生成新对象)可变
线程安全线程安全(不可变特性天然保证)线程安全 (方法加 synchronized锁)线程不安全
执行效率低(频繁修改会产生大量临时对象)中(锁机制导致性能损耗) (无锁,适合单线程场景)
适用场景字符串不频繁修改(如常量定义、字符串拼接次数少)多线程环境下的字符串频繁修改(如日志拼接)单线程环境下的字符串频繁修改(如普通业务逻辑字符串处理)

4. Object 类(所有类的基类)

(1)常用方法
方法名功能描述
getClass()返回当前对象的运行时类对象(Class实例),用于反射
hashCode()返回对象的哈希码(int 值),用于确定对象在哈希表中的存储位置
equals(Object obj)默认比较两个对象的内存地址 (等同于 ==),可重写为比较对象属性
toString()默认返回 “类名 @哈希码” 格式字符串(如 java.lang.Object@123456),可重写为自定义对象描述
wait()让当前线程进入等待状态,需配合 synchronized使用
notify()唤醒当前对象监视器上等待的一个线程,需配合 synchronized使用
(2)==equals()的区别
对比维度==equals()
适用类型基本数据类型、引用数据类型仅引用数据类型
比较逻辑基本数据类型:比较 ;引用数据类型:比较内存地址默认(未重写):比较内存地址(同 ==);重写后:通常比较对象 属性值 (如 String 的 equals()比较字符序列)
(3)hashCode()的作用与原理
  • 作用 :获取哈希码,用于快速确定对象在哈希表(如 HashMap、HashSet)中的存储位置,提升查找效率。
  • 核心原理(以 HashSet 为例)
  1. 向 HashSet 添加对象时,先计算对象的 hashCode(),确定存储位置
  2. 若该位置无对象,直接存入;若有对象(哈希冲突),调用 equals()比较两个对象是否真的相同
  3. equals()返回 true,则视为重复对象,不存入;若返回 false,则通过 “拉链法” 存储在该位置的链表 / 红黑树中
  • 为什么需同时重写 hashCode()equals() :若仅重写 equals(),可能导致 “两个 equals()相等的对象,hashCode()不同”,从而在哈希表中被视为不同对象,破坏集合去重逻辑。

5. java.sql.Date 与 java.util.Date 的区别

对比维度java.util.Datejava.sql.Date
继承关系顶层类(直接继承 Object)子类 (继承 java.util.Date)
功能定位通用日期对象,用于 Java 代码中表示日期时间数据库适配类型,用于与数据库的 DATE类型交互
时间精度可表示年月日时分秒仅表示年月日 (时分秒部分被截断为 0)

二、集合(第十章)

1. 集合框架分类(核心接口)

Java 集合框架基于两大核心接口:Collection(存储单一元素)和 Map(存储键值对),分类如下:

顶层接口子接口 / 实现类核心特性
CollectionList元素不唯一、有序 (按插入顺序),支持索引访问(如 ArrayList、LinkedList)
Set元素唯一、无序 (按哈希值排序),不支持索引(如 HashSet、TreeSet)
Queue元素有序、可重复 ,遵循 “先进先出”(FIFO),仅能在尾部添加、头部获取(如 LinkedList、PriorityQueue)
MapHashMap唯一、无序 ,值可重复;底层哈希表,查询效率高
TreeMap唯一、有序 (按键排序),值可重复;底层红黑树,支持排序查询
Hashtable唯一、无序 ,值可重复;线程安全,不支持 null 键 / 值
LinkedHashMap唯一、有序 (按插入顺序或访问顺序);底层哈希表 + 双向链表,保留键值对顺序

2. 集合底层数据结构总结

集合类底层数据结构JDK 版本差异
List
ArrayListObject[]数组
VectorObject[]数组无(线程安全,方法加 synchronized
LinkedList双向链表JDK1.6 前为循环链表,JDK1.7 后取消循环
Set
HashSet基于 HashMap 实现(存储键,值为固定常量)
LinkedHashSet基于 LinkedHashMap 实现
TreeSet红黑树(按键排序)
Queue
PriorityQueueObject[]数组(基于堆结构)
DelayQueue基于 PriorityQueue 实现
ArrayDeque可扩容动态双向数组
Map
HashMap数组 + 链表(JDK1.8 前);数组 + 链表 / 红黑树(JDK1.8 后,链表长度 > 8 时转红黑树)JDK1.8 优化哈希冲突解决方式
LinkedHashMap哈希表 + 双向链表(继承 HashMap,保留顺序)
Hashtable数组 + 链表
TreeMap红黑树(按键排序)

3. 线程安全的集合

集合类线程安全实现方式特点
Vector方法加 synchronized早期类,性能低,不推荐使用
Hashtable方法加 synchronized不支持 null 键 / 值,性能低,推荐用 ConcurrentHashMap 替代
ConcurrentHashMapJDK1.7:分段锁(Segment);JDK1.8:CAS+ synchronized(粒度更细)高性能,支持并发访问,推荐用于多线程场景

4. Collection 与 Collections 的区别

对比维度CollectionCollections
类型接口工具类 (java.util 包下)
作用定义集合通用操作(如 add()remove()isEmpty()),是 List、Set、Queue 的父接口提供静态工具方法,用于操作集合(如排序、查找、同步控制)
使用方式作为集合类的父接口,通过实现类(如 ArrayList)使用直接调用静态方法(如 Collections.sort(list)

5. 集合遍历方法

  1. 普通 for 循环 :仅适用于 List(支持索引访问),如:
    java

    运行

   for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}
  1. for-each 循环 :适用于所有 Collection(List、Set、Queue)和 Map(需遍历键 / 值集),简洁但无法获取索引,如:
    java

    运行

   for (String str : list) {System.out.println(str);}
  1. 迭代器(Iterator) :适用于所有 Collection,支持遍历中删除元素(避免 ConcurrentModificationException),如:
    java

    运行

   Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String str = iterator.next();if ("delete".equals(str)) {iterator.remove(); // 安全删除}}

6. 核心集合类对比

(1)Vector 与 ArrayList
对比维度VectorArrayList
线程安全安全(方法加 synchronized不安全
扩容机制默认扩容为原容量的2 倍默认扩容为原容量的1.5 倍
版本早期 JDK 类(JDK1.0)替代 Vector 的新类(JDK1.2)
性能低(锁开销)高(无锁)
适用场景多线程环境(不推荐,优先用 ConcurrentArrayList)单线程环境
(2)ArrayList 与 LinkedList
对比维度ArrayListLinkedList
底层结构Object[]数组双向链表
线程安全不安全不安全
访问效率查询快 (索引直接访问,时间复杂度 O (1))查询慢 (需遍历链表,时间复杂度 O (n))
增删效率增删慢 (数组扩容、元素移位,时间复杂度 O (n))增删快 (仅修改指针,时间复杂度 O (1),需先找到位置时 O (n))
空间占用需分配连续内存,可能存在内存浪费(扩容预留空间)每个节点存储元素 + 前后指针,空间占用更灵活
适用场景频繁随机访问、尾部增删频繁中间增删、无需随机访问
(3)HashMap 与 Hashtable
对比维度HashMapHashtable
线程安全不安全安全(方法加 synchronized
null 支持允许 null 键和 null 值不允许 null 键和 null 值
继承关系实现 Map 接口继承 Dictionary 类,实现 Map 接口
版本JDK1.2(新类)JDK1.0(早期类)
性能高(无锁)低(锁开销)
扩容机制默认初始容量 16,扩容为原容量 2 倍默认初始容量 11,扩容为原容量 2 倍 + 1
(4)HashMap 与 HashSet
对比维度HashMapHashSet
存储结构键值对(key-value)单一元素(仅存储 key)
底层实现哈希表(数组 + 链表 / 红黑树)基于 HashMap 实现 (元素作为 key,value 为固定常量 PRESENT
去重逻辑基于 key 的 hashCode()equals()基于元素的 hashCode()equals()(同 HashMap 的 key 去重)
核心方法put(key, value)add(element)(本质是 hashMap.put(element, PRESENT)

7. Comparable 与 Comparator 的区别(排序接口)

对比维度ComparableComparator
所在包java.langjava.util
核心方法compareTo(Object obj)(自身与参数比较)compare(Object obj1, Object obj2)(两个参数比较)
实现方式被比较对象的类实现(如 class User implements Comparable<User>单独定义比较器类 (无需修改被比较对象的源码)
排序灵活性仅支持单一排序规则 (类中固定)支持多排序规则 (可定义多个比较器)
使用场景固定排序逻辑(如 User 类默认按 ID 升序)动态排序逻辑(如有时按年龄升序,有时按姓名降序)

8. 集合关键概念

(1)无序性与不可重复性(以 Set 为例)
  • 无序性 :非 “随机性”,指元素在底层数组中并非按插入顺序存储,而是根据元素的 hashCode()计算存储位置。
  • 不可重复性 :指添加的元素通过 equals()判断为 true时,视为重复元素,不允许存入(如 HashSet 的去重逻辑)。
(2)集合判空

判断集合是否为空, 优先使用 isEmpty()方法 ,而非 size() == 0

  • isEmpty():直接判断内部元素是否为空,效率更高(部分集合底层直接存储空状态标识)
  • size() == 0:需计算元素个数,再与 0 比较,效率略低
  • 可读性:isEmpty()语义更清晰(“是否为空” vs “大小是否为 0”)
(3)Collection 转 Map(Stream 流)

使用 Collectors.toMap()转换时,需注意:

  • 若 value 为 null,会抛出 NullPointerException(Map 接口的部分实现类不支持 null 值,如 HashMap 支持,但 toMap()内部逻辑会校验)
  • 若存在重复 key,会抛出 IllegalStateException,需通过 (k1, k2) -> k1指定冲突解决策略(如保留第一个 key)

9. Collections 工具类常用方法

(1)排序操作
  • void reverse(List list):反转 List 中元素的顺序
  • void shuffle(List list):随机打乱 List 中元素的顺序
  • void sort(List list):按自然顺序(Comparable)对 List 升序排序
  • void sort(List list, Comparator c):按自定义比较器(Comparator)对 List 排序
(2)查找与替换
  • int binarySearch(List list, Object key):对有序 List进行二分查找,返回元素索引(未找到返回负数)
  • Object max(Collection coll):返回 Collection 中按自然顺序排序的最大元素
  • Object min(Collection coll):返回 Collection 中按自然顺序排序的最小元素
  • boolean replaceAll(List list, Object oldVal, Object newVal):将 List 中所有 oldVal 替换为 newVal
(3)同步控制(不推荐)

提供 Collections.synchronizedXXX()方法,将普通集合包装为线程同步集合(如 Collections.synchronizedList(new ArrayList<>())),但 性能极低 (全局锁),推荐使用 JUC 包下的并发集合(如 CopyOnWriteArrayList、ConcurrentHashMap)。

三、IO流(第十一章)

1. IO流的概念与分类

(1)基本概念

IO(Input/Output,输入/输出)指数据在内存外部存储(如文件、数据库、网络) 之间的传输过程。因数据传输具有“连续性”,类似水流,故称为“IO流”。

  • 输入流:数据从外部存储→内存(读操作,如读取本地文件到程序);
  • 输出流:数据从内存→外部存储(写操作,如将程序数据写入本地文件)。
(2)核心分类

IO流按不同维度可分为3类,具体如下:

分类维度具体类型说明
传输方向输入流(InputStream/Reader)读数据,数据源→内存,如 FileInputStream(读文件)、BufferedReader(缓冲读)
输出流(OutputStream/Writer)写数据,内存→数据源,如 FileOutputStream(写文件)、BufferedWriter(缓冲写)
处理单位字节流(InputStream/OutputStream)以“字节”为单位传输(1字节=8位),可处理所有类型文件(如图片、视频、文本)
字符流(Reader/Writer)以“字符”为单位传输(依赖编码,如UTF-8、GBK),仅用于处理文本文件,避免乱码
功能(角色)节点流(直接操作数据源)直接连接数据源/目标,如 FileInputStream(直接读文件)、FileWriter(直接写文件)
处理流(包装节点流)基于节点流扩展功能(如缓冲、编码转换),如 BufferedInputStream(缓冲字节流)、InputStreamReader(字节转字符)

2. 字节流与字符流的区别(为什么分两类)

对比维度字节流(InputStream/OutputStream)字符流(Reader/Writer)
处理单位字节(8位)字符(如UTF-8中1个汉字=3字节)
适用场景所有文件(图片、视频、文本等)仅文本文件(.txt、.java等)
编码依赖不依赖编码,直接操作二进制数据依赖编码(由JVM自动完成“字节→字符”转换),避免乱码
核心问题处理文本时易乱码(如未指定编码)无法处理非文本文件(如图片会损坏)

核心原因:文本文件需考虑“编码格式”(如UTF-8、GBK),字节流直接传输二进制数据,若未手动处理编码,易出现乱码;字符流内置编码转换逻辑,可自动适配编码,更适合文本处理。

3. 序列化与反序列化

(1)基本概念

当需要持久化Java对象(如存到文件)或网络传输Java对象(如RPC调用)时,需将“内存中的对象”转换为“可传输/存储的二进制数据”,此过程即序列化与反序列化:

  • 序列化:内存中的Java对象 → 平台无关的二进制数据(如字节数组),便于存储/传输;
  • 反序列化:二进制数据 → 内存中的Java对象,恢复对象的属性和状态。
(2)实现步骤与注意事项

实现序列化需满足以下条件,否则会抛出 NotSerializableException

  1. 实现 Serializable接口:该接口是“标记接口”(无任何抽象方法),仅用于告诉JVM“该类可序列化”;
  2. 所有成员属性需可序列化:若对象的某个成员是自定义对象类型,该自定义类也必须实现 Serializable
  3. 排除无需序列化的属性:用 transient关键字修饰(而非 staticstatic是类属性,本身不参与对象序列化);
  4. 指定序列化ID(serialVersionUID
    • 作用:保证序列化与反序列化时“对象版本一致”。JVM会对比二进制数据中的 serialVersionUID与本地类的 serialVersionUID,不一致则抛出 InvalidClassException
    • 示例:private static final long serialVersionUID = 1L;(建议显式定义,避免JVM自动生成导致版本冲突)。
(3)排除属性的方式
  • transient关键字修饰属性:被修饰的属性不会参与序列化,反序列化后值为默认值(如 int为0,Stringnull);
  • 示例:private transient String password;(密码无需序列化,避免泄露)。
(4)自定义实现方案(加分)

Java默认序列化存在缺陷:安全漏洞(易被反序列化攻击)、不跨语言(仅Java可解析)、性能差。实际开发中常用主流序列化框架:

  • FastJson:阿里开源,JSON格式序列化,跨语言、性能高;
  • Protobuf:Google开源,二进制格式,压缩率高、性能极强,适合网络传输;
  • Hessian:轻量级二进制序列化,支持跨语言(Java、Python等)。

4. 常用IO流(5对核心流)

IO流的命名有规律(如 FileXXX表示节点流,BufferedXXX表示缓冲处理流),常用核心流如下:

流类型输入流(读)输出流(写)适用场景
字节节点流FileInputStreamFileOutputStream读/写任意文件(图片、视频等)
字节缓冲流BufferedInputStreamBufferedOutputStream包装字节节点流,提升读写效率
字符节点流FileReaderFileWriter读/写文本文件(默认编码)
字符缓冲流BufferedReader(含 readLine()BufferedWriter(含 newLine()包装字符节点流,支持按行读写
字节转字符流InputStreamReader(指定编码)OutputStreamWriter(指定编码)字节流→字符流,解决编码问题

5. 缓冲流的原理与优点

缓冲流(BufferedInputStream/BufferedOutputStreamBufferedReader/BufferedWriter)是典型的“处理流”,核心作用是减少IO次数,提升性能

(1)原理对比
  • 无缓冲流(如 FileInputStream:读1个字节/字符 → 立即写入目标(如文件),“读1次写1次”,频繁操作硬盘,效率低;
  • 缓冲流:内置一个8KB(8*1024字节)的缓冲区(字节缓冲流)或字符缓冲区,读数据时先存入缓冲区,当缓冲区满/手动刷新时,一次性写入目标,大幅减少硬盘IO次数。
(2)核心优点
  1. 提升性能:减少硬盘读写次数(硬盘IO是“慢操作”,内存缓冲是“快操作”);
  2. 简化操作:字符缓冲流提供便捷方法(如 BufferedReader.readLine()按行读,BufferedWriter.newLine()换行);
  3. 保护硬件:减少硬盘频繁读写,降低硬件损耗。

四、多线程(第十二章)

1. 程序、进程、线程的区别

三者是“包含关系”,核心区别在于“资源占用”和“执行粒度”:

概念定义资源占用核心特点
程序用某种语言编写的“静态指令集合”(如 Hello.java不占用资源(未运行)静态、无生命周期
进程系统运行程序的“动态实例”(如双击运行 Hello.exe),是资源分配的基本单位占用独立内存、CPU等资源动态、有生命周期(启动→运行→消亡),进程间资源隔离
线程进程内的“执行路径”,是资源调度的基本单位(一个进程可包含多个线程)共享进程的内存、资源(如堆)轻量级、切换成本低(比进程小1-2个数量级)

示例:打开Chrome浏览器是1个进程,浏览器中的“标签页”“下载任务”是该进程下的多个线程。

2. 创建线程的4种方式

(1)继承 Thread
  • 步骤:继承 Thread → 重写 run()(定义线程任务) → 创建实例→调用 start()(启动线程,JVM调用 run());
  • 示例:
    class MyThread extends Thread {@Overridepublic void run() {System.out.println("线程任务:继承Thread");}
    }
    // 启动
    new MyThread().start();
    
  • 缺点:Java单继承,继承 Thread后无法再继承其他类。
(2)实现 Runnable接口
  • 步骤:实现 Runnable → 重写 run() → 把 Runnable实例传给 Thread构造器→调用 start()
  • 示例:
    class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("线程任务:实现Runnable");}
    }
    // 启动
    new Thread(new MyRunnable()).start();
    
  • 优点:支持多实现,可继承其他类,解耦“任务”与“线程”。
(3)实现 Callable接口(带返回值)
  • 特点:Runnable的增强版,call()方法可返回值、抛出异常;需配合 FutureTask(实现 Runnable)使用;
  • 步骤:实现 Callable<T> → 重写 call() → 封装为 FutureTask → 传给 Thread→调用 start()→用 get()获取返回值;
  • 示例:
    class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {return 100; // 返回值}
    }
    // 启动与获取结果
    FutureTask<Integer> task = new FutureTask<>(new MyCallable());
    new Thread(task).start();
    Integer result = task.get(); // 阻塞获取返回值
    
(4)使用线程池(推荐,JDK1.5+)
  • 核心类:ThreadPoolExecutor(自定义线程池)、Executors(工具类,快速创建线程池,如 Executors.newFixedThreadPool(5));
  • 优点:
    1. 降低资源消耗(复用线程,避免频繁创建/销毁线程);
    2. 提高响应速度(线程已创建,无需等待初始化);
    3. 便于管理(控制线程数量、任务队列等);
  • 示例:
    // 创建固定线程数的线程池
    ExecutorService pool = Executors.newFixedThreadPool(3);
    // 提交任务
    pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("线程池任务");}
    });
    // 关闭线程池
    pool.shutdown();
    

3. 线程的启动与停止

(1)启动线程
  • 必须调用 Threadstart()方法,而非直接调用 run()
  • 原因:start()会向JVM注册线程,由JVM调度CPU时间片后执行 run();直接调用 run()只是“普通方法调用”,不会启动新线程。
(2)停止线程(安全方式)
  • 不推荐的方式stop()(已废弃)、suspend()/resume()(易导致死锁),会强制终止线程,释放锁资源,导致数据不一致;
  • 推荐的方式:用“中断标记”控制线程退出(interrupt()+isInterrupted()),或通过 volatile变量标记;
  • 示例(中断标记):
    class StopThread implements Runnable {@Overridepublic void run() {// 检查线程是否被中断while (!Thread.currentThread().isInterrupted()) {try {Thread.sleep(1000); // 阻塞时,interrupt()会抛出异常System.out.println("线程运行中");} catch (InterruptedException e) {// 捕获异常后,需手动重置中断标记(否则isInterrupted()会返回false)Thread.currentThread().interrupt();break; // 退出循环,停止线程}}}
    }
    // 停止线程
    Thread t = new Thread(new StopThread());
    t.start();
    Thread.sleep(3000);
    t.interrupt(); // 标记线程中断
    

4. 线程的生命周期(7种状态,JDK官方定义)

线程从创建到消亡的完整状态流转,由 Thread.State枚举定义,共7种状态:

状态说明触发条件(状态转换)
新建(New)线程对象已创建(new Thread()),但未调用 start()调用 start() → 就绪(Runnable)
就绪(Runnable)线程已注册到JVM,等待CPU时间片(具备执行条件,但未执行)CPU分配时间片 → 运行(Running)
运行(Running)线程获得CPU时间片,执行 run()方法中的任务时间片用完 → 就绪;调用 wait() → 等待;竞争锁失败 → 阻塞
阻塞(Blocked)线程因“竞争同步锁失败”(如 synchronized)或“I/O操作”暂停执行锁释放 → 就绪;I/O完成 → 就绪
等待(Waiting)线程主动释放CPU(如 wait()join()),需被其他线程唤醒其他线程调用 notify()/notifyAll() → 就绪
超时等待(Timed Waiting)线程主动释放CPU,但有超时时间(如 sleep(1000)wait(1000)超时时间到 → 就绪;其他线程唤醒 → 就绪
终止(Terminated)线程执行完毕(run()结束)或异常终止(抛出未捕获异常)无(状态不可逆,无法重启)

简化版5种状态:将“等待、超时等待”归为“阻塞”,即:新建 → 就绪 → 运行 → 阻塞 → 终止。

5. 线程阻塞(Blocked)与等待(Waiting)的区别

对比维度阻塞(Blocked)等待(Waiting)
触发条件竞争同步锁失败(如 synchronized)、I/O操作调用 wait()(无超时)、join()(无超时)
资源竞争会竞争锁资源,等待锁释放不竞争锁资源,主动放弃CPU
唤醒机制锁释放后,JVM自动唤醒(无需显式调用)需其他线程显式调用 notify()/notifyAll()
典型场景多线程竞争同一 synchronized线程A等待线程B执行完毕(B.join()

6. wait()sleep()的区别(高频)

对比维度wait()sleep(long millis)
所属类Object类(所有对象都有)Thread类(静态方法)
锁释放调用后立即释放持有的对象锁不释放任何锁(即使持有锁)
使用场景多线程间通信(如生产者-消费者模型)线程休眠(如模拟延迟)
调用条件必须在 synchronized块/方法中(需持有对象锁)可在任意代码中调用(无需锁)
唤醒方式其他线程调用 notify()/notifyAll(),或超时(wait(long)超时自动唤醒,或 interrupt()中断

7. 多线程的意义与问题

(1)为什么用多线程?

多线程的核心价值在于提升资源利用率满足业务场景需求,具体可从底层原理和实际开发两方面分析:

1. 底层:充分利用硬件资源,降低执行成本

  • CPU利用率最大化:现代计算机多为多核CPU,若采用单线程,仅能使用一个核心,其他核心处于空闲状态(如单线程程序执行IO操作时,CPU会闲置等待)。多线程可将任务分配到多个核心,让CPU“并行工作”,避免资源浪费。示例:文件下载时,单线程需等待“读取网络数据→写入本地文件”完成后再继续;多线程可拆分文件块,同时从网络读取不同块并写入,CPU和IO设备并行运作。
  • 线程切换成本低于进程:线程是“轻量级进程”,共享所属进程的内存空间(如堆、方法区),切换时无需重新分配内存和资源;而进程切换需切换整个地址空间,成本是线程的1-2个数量级。多线程可在同一进程内快速切换,提升系统响应速度。

2. 业务:满足高并发、异步化、复杂场景需求

  • 高并发处理:互联网系统需同时应对大量请求(如电商秒杀、直播弹幕),单线程无法及时处理所有请求,会导致响应超时。多线程可并行处理多个请求,提升系统吞吐量(单位时间处理的请求数)。示例:Tomcat服务器通过“线程池”管理线程,每个请求分配一个线程处理,支持同时处理上千个客户端连接。
  • 异步化解耦:避免“同步阻塞”导致的流程卡顿,将耗时操作(如IO、数据库查询)异步执行,主线程可继续处理其他任务,提升用户体验。示例:用户注册时,主线程完成“数据校验→写入数据库”后立即返回“注册成功”,同时启动子线程异步发送“欢迎短信”,无需用户等待短信发送完成。
  • 复杂任务拆分:将大型任务拆分为多个子任务,用多线程并行执行,缩短总耗时。
    示例:Excel数据导入时,单线程需逐行读取并处理;多线程可按行号拆分数据块,每个线程处理一块数据,处理时间从“10分钟”缩短至“2分钟”。

(2)线程并发可能带来的问题(高频)

多线程虽能提升效率,但因“线程共享进程资源”且“CPU调度无序”,会引入线程安全资源竞争相关问题,常见如下:

1. 线程不安全:数据一致性问题

当多个线程同时操作“共享变量”(如静态变量、堆中对象属性)时,若未加同步控制,会导致“脏读”“幻读”“数据覆盖”等问题。
示例:两个线程同时对“count=0”执行 count++(实际分3步:读取count→加1→写入count),可能出现“线程1读count=0,线程2也读count=0,最终两者都写入1,count=1而非2”的结果。

2. 死锁:线程永久阻塞

  • 定义:多个线程互相持有对方需要的资源(如锁),且都不释放,导致所有线程永久阻塞,程序无法继续执行。
  • 死锁产生的4个必要条件(缺一不可):
    1. 互斥条件:资源只能被一个线程占用(如 synchronized锁);
    2. 请求与保持条件:线程持有一个资源后,又请求其他资源,且不释放已持有资源;
    3. 不剥夺条件:线程已持有的资源,不能被其他线程强制剥夺;
    4. 循环等待条件:多个线程形成“资源请求循环”(如线程A等线程B的锁,线程B等线程A的锁)。

示例:线程1持有锁A,请求锁B;线程2持有锁B,请求锁A,两者互相等待,形成死锁。

3. 内存泄漏:资源无法回收

线程未正确关闭或资源未释放,导致JVM无法回收内存,长期积累会导致内存溢出(OOM)。
示例:线程中创建的 ThreadLocal变量未调用 remove(),线程结束后 ThreadLocal实例仍被线程的 ThreadLocalMap引用,若线程是线程池中的复用线程,内存会持续泄漏。

4. 上下文切换开销

CPU调度线程时,需保存当前线程的“上下文信息”(如程序计数器、寄存器值),切换到新线程时再恢复其上下文。频繁的上下文切换会消耗CPU资源,反而降低程序效率。
示例:线程数远超CPU核心数(如1000个线程跑在8核CPU上),CPU会频繁切换线程,大部分时间用于保存/恢复上下文,而非执行任务。


文章转载自:

http://CMN22IG0.mLnzx.cn
http://6iSq6Jn7.mLnzx.cn
http://bSSZm2LA.mLnzx.cn
http://8pjh67Z2.mLnzx.cn
http://ypyjEOPE.mLnzx.cn
http://D3zj6IBe.mLnzx.cn
http://iOg9waPt.mLnzx.cn
http://7BuBdB7p.mLnzx.cn
http://MhOvwSUn.mLnzx.cn
http://hlZrmE6g.mLnzx.cn
http://JLccXGdo.mLnzx.cn
http://0aGvcnXI.mLnzx.cn
http://umSw4B2b.mLnzx.cn
http://IDlSdjEg.mLnzx.cn
http://MSmPN5Xz.mLnzx.cn
http://Csw3QmkL.mLnzx.cn
http://DLSY7ZiN.mLnzx.cn
http://21n3vxhj.mLnzx.cn
http://zDpbjCCn.mLnzx.cn
http://2EsQj6yP.mLnzx.cn
http://Fa92qbU0.mLnzx.cn
http://Lei1EZu2.mLnzx.cn
http://STZavWfb.mLnzx.cn
http://AtVOjZzp.mLnzx.cn
http://Es2QnRKy.mLnzx.cn
http://tLzlKqc2.mLnzx.cn
http://WIfduZ8t.mLnzx.cn
http://aBhdo2IY.mLnzx.cn
http://V1z0oN0U.mLnzx.cn
http://COOsqSld.mLnzx.cn
http://www.dtcms.com/a/383902.html

相关文章:

  • 学习日报|线程池 OOM 案例与优化思路
  • HOT100--Day25--84. 柱状图中最大的矩形,215. 数组中的第K个最大元素,347. 前 K 个高频元素
  • Linux网络:socket编程UDP
  • GeoHash分级索引技术
  • RISC与CISC:ARM指令集解析
  • 第十二篇:Qcom Camx打印实时帧率 FPS
  • 【开题答辩全过程】以 “候鸟式养老机构”管理系统的设计与实践为例,包含答辩的问题和答案
  • 造车阶段解读
  • 技术论文分析分析论文《计算机病毒判定专家系统原理与设计》思考其在游戏中的应用
  • Elasticsearch面试精讲 Day 18:内存管理与JVM调优
  • Android开发-文本输入
  • C++启航:从0到1,解锁面向对象编程的第一把密钥
  • 基于Dash和Plotly的交互式人体肌肉评分可视化系统[附源码】
  • Linux 开发工具(2)
  • Java进阶教程,全面剖析Java多线程编程,什么是多线程,笔记01
  • 论文参考文献交叉引用+中括号变成上标+自动生成目录方法
  • Linux:8_库制作与原理
  • Codeforces Round 1047 Div.3 DEFG补题
  • OWASP Top 10 最新版
  • 【脑电分析系列】第9篇:时频分析利器 — 小波变换与事件相关谱扰动(ERSP)的应用
  • struct的一些函数以及其他用法(析构、友元、构造、成员等)
  • c语言中实现线程同步的操作
  • 【Java后端】Spring Boot 2.7.x 和 Swagger 3.0.x (springfox 3.x) 的兼容性问题
  • Springboot的自动配置原理?
  • 9 月 13 日科技前沿大揭秘:多领域创新闪耀
  • 基于少样本支持的一类学习的增量式生成对抗诊断:
  • TDengine 特殊选择函数 UNIQUE 用户手册
  • 状态机SMACH相关教程介绍与应用案例分析——机器人操作进阶系列 · 状态机篇
  • Transformer简介
  • 维星AI-AI驱动的精准获客:重塑数字营销新范式