java八股---java基础04(集合、异常、引用、线程)
面向对象
面向对象和面向过程的区别
面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候 一 一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发
面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤, 而是为了描述某个事物在解决整个问题的过程中所发生的行为。
面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。
创建对象有几种方式?
java 中提供了以下四种创建对象的方式:
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
instanceof关键字的作用
instanceof 是 Java 中的一个关键字,用于检查一个对象是否是指定类或其子类的实例。它通常用于在运行时判断对象的类型,以避免类型转换时出现 ClassCastException 异常。
instanceof 的主要作用是:
类型检查:判断一个对象是否属于某个类或其子类。
类型安全:在类型转换之前进行检查,避免运行时异常。
深拷贝和浅拷贝的区别是什么?
深拷贝(Deep Copy) 和 浅拷贝(Shallow Copy) 是两种常见的对象复制方式,它们的区别在于 对象内部引用类型成员 的处理方式。
浅拷贝
浅拷贝是指创建一个新对象,但对原始对象的 基本类型成员变量 和 引用类型成员变量 进行拷贝时,仅复制对象的 引用,而不复制对象本身。也就是说,浅拷贝会生成一个新的对象,但其中的引用类型成员变量仍然指向原对象中相同的内存地址。
特点:
- 对于基本类型,直接复制其值。
- 对于引用类型,复制的是引用地址,即原对象和新对象的引用成员变量指向同一块内存空间。
- 修改新对象中的引用类型成员,可能会影响原对象。
深拷贝
深拷贝是指创建一个新对象,并且递归地复制原对象的所有字段(包括对象中的引用类型成员变量)。在深拷贝中,不仅仅是拷贝原对象的值,而是拷贝整个对象的内容,包括对象内部的所有嵌套对象。深拷贝确保新对象中的引用类型成员与原对象的引用类型成员指向不同的内存地址,即使是嵌套的对象也会创建新的实例。
特点:
- 对于基本类型,直接复制其值。
- 对于引用类型,创建新的对象并复制引用类型对象的内容。
- 修改新对象中的引用类型成员,不会影响原对象。
- 浅拷贝 只复制对象的引用类型字段,可能导致副本和原对象共享同一块内存区域,因此修改副本的引用类型字段可能影响原对象。
- 深拷贝 复制对象及其所有引用类型字段,确保副本和原对象完全独立,修改副本的引用类型字段不会影响原对象。
异常
try catch finally ,try里面有return ,finally 还执行吗
是的,即使
try
块中有return
语句,finally
块仍然会执行。不过,finally
块中的代码执行顺序和return
的行为有些复杂。执行顺序:
try
块:当try
块中的代码执行完毕时,Java 会执行finally
块中的代码,即使try
块中有return
语句。finally
块:finally
中的代码会在try
或catch
块中抛出异常后执行,即使有return
,finally
中的代码也会执行。return
语句:如果try
块或catch
块中执行了return
,它会先把返回值准备好,但在返回之前,finally
会先执行。finally
块中的代码执行完毕后,才会返回try
或catch
块中的return
语句的值。
Exception和Error包结构
在 Java 中,异常(Exception)和错误(Error)是两种不同类型的问题,它们都继承自Throwable 类。
Java 提供了异常处理机制来捕获和处理程序运行时可能出现的异常,但对于 Error,通常不应该被程序捕获和处理。理解它们的区别是理解 Java 错误处理机制的基础。
异常(Exception)
Exception
是 Java 中用来表示程序运行中可能发生的异常情况的类。它通常用于程序的正常错误处理流程。
Exception
又可以分为两类:
- 检查型异常(Checked Exception):程序必须显式捕获和处理的异常(编译时就会检查),否则编译错误。例如:
IOException
、SQLException
。- 非检查型异常(Unchecked Exception):程序可以选择性地捕获的异常,通常不需要强制捕获或声明。例如:
RuntimeException
及其子类(如NullPointerException
、ArrayIndexOutOfBoundsException
)常见的
Exception
子类:
IOException
:输入输出异常。SQLException
:数据库访问异常。NullPointerException
:空指针异常(通常是非检查型异常)。ArithmeticException
:算术运算异常(例如除以零)。错误(Error)
Error
表示程序遇到的严重错误,这些错误通常由 Java 虚拟机(JVM)引起,且通常不可恢复。Error
类及其子类的异常,程序不应该去捕获它们,因为它们通常表示虚拟机或系统级的错误,捕获它们无法帮助程序恢复或正常运行。常见的
Error
子类:
OutOfMemoryError
:内存溢出错误。StackOverflowError
:栈溢出错误,通常是递归调用过深。VirtualMachineError
:虚拟机级别的错误。
异常(Exception)和错误(Error)的区别
Exception
用于表示程序的正常异常情况,通常可以被捕获和处理,确保程序可以在异常情况下恢复或继续运行。Error
用于表示系统级别的错误,通常是无法恢复的,程序员不应该去捕获这些错误。
OOM你遇到过哪些情况
在开发和运维中,
OutOfMemoryError
(OOM) 是一个常见且令人头痛的问题。我在处理这类问题时,遇到过一些典型的情况。以下是几种常见的OOM
场景和背后的原因:1、大量对象的创建导致堆内存不足
情况描述:有些应用程序会在短时间内创建大量对象,特别是没有及时释放的对象,最终导致堆内存填满,抛出
OutOfMemoryError: Java heap space
。常见原因:
- 程序在循环中创建了大量临时对象(比如字符串、集合等),这些对象没有被及时回收。
- 例如,大规模的
List
或Map
在未被清理的情况下,持续增加数据,最终导致堆溢出。解决方法:
- 使用内存分析工具(如 VisualVM、JProfiler、MAT)来检测内存泄漏。
- 优化对象创建,避免在循环中创建大量不必要的对象。
- 使用合适的数据结构,避免存储过多的临时数据。
2、大数据加载到内存中
情况描述:在处理大量数据(如从文件、数据库或网络中加载数据时),如果没有进行分批处理,整个数据集被一次性加载到内存中,容易导致堆内存溢出。
常见原因:
- 一次性将一个很大的文件或数据库查询结果加载到内存中。
- 未做分页或流式处理,而是将所有数据加载到内存中。
解决方法:
- 对于大数据集,使用分页加载或流式处理(如 Java Streams 或
BufferedReader
)。- 处理数据时按批次进行加载,而不是一次性将所有数据加载到内存中。
3、递归深度过大
- 情况描述:递归函数调用深度过大,导致栈内存不足,抛出
StackOverflowError
,有时会间接引发OutOfMemoryError
。- 常见原因:
- 递归调用的条件不合理,导致递归深度过大,堆栈不断增长。
- 解决方法:
- 限制递归的最大深度,确保递归有终止条件。
- 如果递归较深,可以考虑使用迭代来替代递归,减少栈内存的使用。
SOF你遇到过哪些情况
StackOverflowError
(SOF) 是 Java 中的一种错误,表示 Java 虚拟机 (JVM) 中的栈内存不足。通常发生在深度递归或无限递归的情况下。
栈内存用于存储方法调用时的局部变量、操作数栈等内容。当栈的深度超出了 JVM 配置的栈大小限制时,就会抛出
StackOverflowError
。1. 无限递归调用
情况描述:最常见的
StackOverflowError
情况是因为递归函数没有正确的终止条件,导致方法被不断调用,栈内存超出限制。常见原因:
- 递归函数没有正确的退出条件,导致函数一直调用自己。例如,递归条件有误,导致无限递归。
- 错误的递归基准条件,例如在条件未满足时仍然继续递归调用。
解决方法:
- 确保递归函数有明确的基准条件,并且在合适的时机终止递归。
- 进行递归深度的检查,避免递归层级过深。
- 对于某些可以用循环实现的递归,考虑使用迭代替代递归,减少栈内存的使用。
2、递归过深导致栈溢出
情况描述:递归函数的调用深度过大,即使递归条件正确,栈的深度仍然超出了默认配置的栈内存限制,导致
StackOverflowError
。常见原因:
- 大数据问题,递归处理的数据量很大,导致每次递归调用时需要保存大量的局部变量,栈内存逐渐消耗殆尽。
- 没有优化递归结构,导致栈内存逐渐增加直到溢出。
解决方法:
- 限制递归的最大深度,避免栈深度过大。
- 在调用递归时,尽量减少栈的使用,例如使用尾递归优化。
- 考虑将递归改为迭代形式,减少对栈的依赖。
3、大量方法调用
- 情况描述:在没有递归的情况下,程序中也可能因为方法调用过多,导致栈溢出。例如,代码中存在过多的嵌套方法调用,栈内存被占用过多。
- 常见原因:
- 递归调用、方法嵌套调用等,层层调用时栈帧逐渐增多,超出栈的限制。
- 解决方法:
- 尽量避免不必要的深度方法调用,避免方法嵌套过深。
- 使用堆栈优化技术,降低栈的消耗。
说说你平时是怎么处理 异常的
try-catch-finally
- try 块负责监控可能出现异常的代码
- catch 块负责捕获可能出现的异常,并进行处理
- finally 块负责清理各种资源,不管是否出现异常都会执行
- 其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程
集合
ArrayList的底层工作原理
- 1在构造ArrayList时,如果没有指定容量,那么内部会构造一个空数组,如果指定了容量,那么就构造出对应容量大小的数组
- 2在添加元素时,会先判断数组容量是否足够,如果不够则会扩容,扩容按1.5倍扩容,容量足够后,再把元素添加到数组中
- 3在添加元素时,如果指定了下标,先检查下标是否越界,然后再确认数组容量是否足够,不够则扩容,然后再把新元素添加到指定位置,如果该位置后面有元素则后移
- 4再获取指定下标的元素时,先检查下标是否越界,然后从数组中取出对应位置的元素
ArrayList 和 LinkedList的区别
ArrayList 和 LinkedList 是 Java 中两种常用的集合类,它们都实现了 List 接口,但在底层实现、性能特性和适用场景上有显著区别。以下是它们的详细对比:
底层实现:
特性 ArrayList LinkedList 数据结构 动态数组 双向链表 内存占用 内存连续,占用较少 内存不连续,占用较多(需要存储前后节点指针) 扩容机制 当容量不足时,自动扩容(通常是 1.5 倍) 不需要扩容,动态添加节点
特性 ArrayList
LinkedList
随机访问 适合频繁按索引访问元素的场景 不适合 插入/删除 适合尾部插入/删除,不适合头部和中间操作 适合频繁在头部或中间插入/删除的场景 内存占用 占用较少 占用较多 遍历性能 适合遍历 适合使用迭代器遍历
特性 ArrayList
LinkedList
线程安全性 非线程安全 非线程安全 实现接口 实现了 List
接口实现了 List
和Deque
接口额外功能 无 支持队列操作(如 addFirst
、addLast
)
HashMap和hashtable的区别
初始容量和扩容机制
特性 HashMap
Hashtable
线程安全 非线程安全 线程安全 性能 较高 较低 null
键值允许 不允许 迭代器 快速失败 非快速失败 继承关系 继承自 AbstractMap
继承自 Dictionary
使用场景 单线程环境 多线程环境 HashMap:适合单线程环境,性能高,允许 null 键值。
Hashtable:适合多线程环境,性能较低,不允许 null 键值。
推荐:在单线程环境下使用 HashMap,在多线程环境下使用 ConcurrentHashMap。
Collection包结构,与Collections的区别
在 Java 中,Collection 和 Collections 都与集合相关,但它们有不同的作用和结构。
1、Collection 接口
Collection 是 Java 集合框架中最顶层的接口,它位于 java.util 包中,是 所有集合类 的父接口。几乎所有的集合类(如 List、Set、Queue)都继承或实现了 Collection 接口。Collection 定义了集合的基本操作,如添加、删除、检查元素等。
2、Collections 工具类
Collections 是一个 工具类,位于 java.util 包中,提供了用于操作集合的静态方法。它提供了很多常用的集合操作,如排序、查找、同步化集合、反转等方法。Collections 类的所有方法都是静态的,操作的是传入的集合对象。
Collection
是一个接口,表示集合框架中所有集合类的通用接口。Collections
是一个工具类,提供了许多操作集合的静态方法,用于对集合进行排序、查找、同步等操作。
说说List、set、Map三者的区别?
List
特点:
- 有序:
List
中的元素有顺序,按照插入顺序保存。- 允许重复:可以存储重复的元素。
- 索引访问:可以通过索引来访问元素(类似数组)。
常见实现:
ArrayList
、LinkedList
。Set
特点:
- 无序:
Set
中的元素没有顺序(具体实现如HashSet
会使用哈希算法来存储元素,所以元素的顺序是不确定的)。- 不允许重复:
Set
中的元素不能重复。常见实现:
HashSet
、LinkedHashSet
、TreeSet
。Map
特点:
- 无序(具体实现如
HashMap
中的键值对无顺序)。- 键-值对存储:
Map
中的元素是键值对 (key-value
),每个key
对应一个value
。- 键唯一:每个键在
Map
中是唯一的,但不同的键可以对应相同的值。常见实现:
HashMap
、TreeMap
、LinkedHashMap
。总结:
- List:有序,允许重复元素,支持通过索引访问。
- Set:无序,不允许重复元素。
- Map:键-值对存储,键唯一,值可以重复。
有数组了为什么还要搞个 ArrayList呢?
数组 和 ArrayList 都可以用来存储一组数据
数组:在声明时就需要确定大小,大小一旦确定就不可更改。如果你想在数组中添加或删除元素,就需要手动调整数组的大小,甚至可能需要创建一个新的数组来存储更多的元素。
ArrayList:是一个 动态数组,可以自动调整大小。当你向 ArrayList 中添加元素时,它会自动扩展,不需要你手动管理大小。
总结:
- 数组 适用于:元素数量固定、对性能要求非常高、操作简单的场景(如数学计算、算法实现等)。
ArrayList
适用于:元素数量不固定、需要动态调整大小、常用操作频繁(如增删查找)的场景,或者当你需要更方便、更通用的 API 时。所以,尽管数组在很多情况下很有效,但
ArrayList
提供了更多的灵活性和方便的操作,它适合那些需要动态大小和更复杂操作的场景。
什么是fail-fast 机制
Fail-fast 机制 是一种在程序运行过程中,尽早发现错误并立即中断的机制。这个机制的核心思想是 在出现潜在错误的第一时间就抛出异常,避免错误被悄无声息地传播,使得程序在问题发生的早期就能够停止,从而减少问题的复杂性和排查难度。
在 Java 中,很多集合类(如 ArrayList、HashMap)实现了 fail-fast 机制,尤其是在多线程环境下。它们会在某些操作(如迭代)中发现并发修改的情况时,抛出异常 来避免数据不一致的问题。
Fail-fast 机制的工作原理
- 当你迭代一个集合(如
ArrayList
)时,集合的 结构可能发生变化,例如在迭代过程中进行 添加、删除元素 等修改操作。- 如果集合发现其结构已经被修改,不通过预定的迭代方式,它会立即抛出一个
ConcurrentModificationException
异常,这就是 fail-fast 机制的表现。- 这种机制帮助开发人员在早期发现程序中的潜在并发错误或逻辑错误,避免了更复杂的故障。
谈谈ConcurrentHashMap的扩容机制
1.7版本
- 1.7版本的ConcurrentHashMap是基于Segment分段实现的
- 每个Segment相对于一个小型的HashMap
- 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
- 先生成新的数组,然后转移元素到新数组中
- 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8版本
- 1.8版本的ConcurrentHashMap不再基于Segment实现
- 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
- 如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
- ConcurrentHashMap是支持多个线程同时扩容的
- 扩容之前也先生成一个新的数组
- 在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作
Jdk1.7到Jdk1.8 HashMap 发生了什么变化(底层)?
- 1.7中底层是数组+链表,
1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率- 1.7中链表插入使用的是头插法,
1.8中链表插入使用的是尾插法,因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用尾插法- 1.7中哈希算法比较复杂,存在各种右移与异或运算,
1.8中进行了简化,因为复杂的哈希算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源
HashMap的扩容机制原理
1.7版本
- 1先生成新数组
- 2遍历老数组中的每个位置上的链表上的每个元素
- 3取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
- 4将元素添加到新数组中去
- 5所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本
- 1先生成新数组
- 2遍历老数组中的每个位置上的链表或红黑树
- 3如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
- 4如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
- a统计每个下标位置的元素个数
- b如果该位置下的元素个数超过了 8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置
- c如果该位置下的元素个数没有超过6,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置
- 5所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
说一下HashMap的Put方法
先说HashMap的Put方法的大体流程:
- 根据Key通过哈希算法与与运算得出数组下标
- 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置
- 如果数组下标位置元素不为空,则要分情况讨论
- a如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
- b如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
- ⅰ如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
- ⅱ如果此位置上的Node对象是链表节点,则将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红黑树
- ⅲ将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法
Fail-fast 的优缺点
优点:
- 及时发现问题:程序在问题发生的最早阶段就会终止,帮助开发人员更早地发现和修复问题。
- 简化调试:可以避免错误在程序中蔓延,使得错误在发生时即被捕获,更容易定位问题。
- 提高程序的可靠性:防止了潜在的数据不一致或状态异常,确保数据的正确性。
缺点:
- 性能开销:需要在每次操作时检查集合是否被修改,因此可能会带来一定的性能开销,尤其是在高并发场景下。
- 中断执行:由于程序会在遇到问题时立即终止,这可能会导致某些任务没有完成,可能需要额外的错误处理机制。
红黑树有哪几个特征?
红黑树(Red-Black Tree)是一种自平衡的二叉查找树(Binary Search Tree),它通过对节点的颜色进行标记(红色或黑色)来保持树的平衡,从而确保基本操作(如插入、删除、查找等)的时间复杂度为 O(logn)O(\log n)O(logn)。红黑树有以下几个重要的特征:
1、每个节点要么是红色,要么是黑色。
- 每个节点都有一个颜色属性,可以是红色或黑色。
2、根节点是黑色的。
- 红黑树的根节点必须是黑色的。这样可以保持树的整体平衡,避免因为红色节点的存在过多而导致高度不平衡。
3、每个叶子节点(NIL 节点)都是黑色的。
- 叶子节点并不存储实际数据,通常是指向
null
的虚拟节点。所有的叶子节点都被认为是黑色。4、红色节点不能有红色的子节点(即红色节点不能连续出现)。
- 也就是说,在红黑树中,红色节点不能与红色节点相连,红色节点的父节点和子节点必须是黑色的。这一规则确保了树的高度不会过高,从而避免了树变得过于倾斜。
5、从任一节点到其所有后代叶子节点的路径上,必须包含相同数目的黑色节点。
- 这是红黑树最重要的平衡条件。具体来说,从根节点到每个叶子节点的路径中,黑色节点的数量必须相同。这一特性确保了树的高度不会超过 2logn2 \log n2logn,从而保证了操作的对数时间复杂度。
引用
java的四种引用,强弱软虚
在 Java 中,引用类型决定了对象的垃圾回收行为。Java 提供了四种不同的引用类型:强引用、软引用、弱引用、虚引用。这些引用类型的区别主要在于垃圾回收器何时回收它们所指向的对象。
强引用(Strong Reference)
这是最常见的引用类型,也是默认的引用类型。如果一个对象被强引用所引用,那么垃圾回收器永远不会回收这个对象,直到引用被显式地设置为
null
或者该对象超出作用域。特点:
- 最普通的引用方式,通常通过赋值给变量来创建。
- 垃圾回收器不会回收强引用指向的对象,直到不再有任何强引用指向它。
在这个例子中,
str
就是强引用。只要str
引用着这个字符串对象,该对象就不会被垃圾回收。String str = new String("Hello");
2. 软引用(Soft Reference)
软引用是一种较弱的引用,当系统内存不足时,垃圾回收器会回收这些对象。软引用适用于缓存实现。如果系统有足够的内存,软引用所指向的对象会一直保留在内存中。
特点:
- 软引用指向的对象在垃圾回收时只有在内存不足时才会被回收。
- 适用于缓存等场景,当内存空间紧张时,缓存中的对象会被回收。
使用:
SoftReference
类提供了软引用的功能在这个例子中,
softRef
是对String
对象的软引用。只有在 JVM 内存不足时,该对象才可能被回收。SoftReference<String> softRef = new SoftReference<>(new String("Hello"));
3. 弱引用(Weak Reference)
弱引用的强度更弱,当垃圾回收器运行时,不管系统是否内存不足,只要一个对象只被弱引用所引用,那么这个对象就会被回收。
特点:
- 当一个对象仅被弱引用所引用时,垃圾回收器在下次回收时一定会回收该对象。
- 适用于一些短生命周期的对象,例如某些缓存项或者类似的对象。
使用:
WeakReference
类提供了弱引用的功能。在这个例子中,
weakRef
是对String
对象的弱引用。无论系统内存是否紧张,只要没有强引用指向该对象,垃圾回收器会在下次回收时清除该对象。WeakReference<String> weakRef = new WeakReference<>(new String("Hello"));
4. 虚引用(Phantom Reference)
虚引用是最弱的一种引用。虚引用的对象在被垃圾回收时会被置为
null
,虚引用并不能决定对象是否会被回收。它主要用于跟踪对象的垃圾回收过程。特点:
- 虚引用不会影响对象的生命周期,虚引用所指向的对象会在垃圾回收时被清除。
- 通常用于一些特定的场景,例如对象的清理工作或监控对象的回收时机。
使用:
PhantomReference
类提供了虚引用的功能,并且必须与引用队列 (ReferenceQueue
) 配合使用。在这个例子中,
phantomRef
是对String
对象的虚引用。无论何时该对象被垃圾回收,虚引用所指向的对象都会被放入引用队列。
四种引用对比
应用场景:
- 强引用:用于普通对象的引用,最常见。
- 软引用:适用于缓存,内存不足时能够回收缓存对象。
- 弱引用:适用于需要在内存不足时回收的对象,或一些临时性的对象。
- 虚引用:用于跟踪对象的生命周期,进行清理或其他后续操作,主要用于垃圾回收后的回调。
通过合理使用这些引用类型,可以优化内存管理,尤其是在需要缓存、大量对象创建和垃圾回收时。
线程
简述线程、程序、进程的基本概念。
程序 (Program)
定义:程序是指一组有序的指令集合,它是静态的、可执行的代码,通常存储在磁盘上。程序本身并不执行,它只是一个存储在硬盘上的文件(例如
.exe
文件或者.jar
文件)。特点:
- 程序是静态的,存储在磁盘或其他持久化介质上。
- 它是开发人员编写的代码,并且当运行时会被加载到内存中。
例子:MyApp.exe、calculator.jar、notepad.exe。
进程 (Process)
定义:进程是程序在计算机中的一次执行实例,它是程序执行的动态过程。每个进程都有自己独立的内存空间、系统资源、文件描述符等。进程由操作系统进行管理,是程序的一个运行时实例。
特点:
- 进程是操作系统调度和管理的基本单位。
- 每个进程都有自己独立的地址空间,不同进程之间的数据不直接共享。
- 进程内有多个线程,每个线程都可以独立执行代码。
例子:一个运行中的文本编辑器、浏览器、游戏等,每个运行的应用程序都至少有一个进程。
线程 (Thread)
定义:线程是进程中的最小执行单位。一个进程中可以包含多个线程,这些线程共享进程的资源,如内存空间。线程是程序执行的实际载体,它代表了程序中执行的具体任务。
特点:
- 线程是CPU调度和执行的最小单位。
- 同一进程中的多个线程共享同一块内存空间。
- 线程之间可以并发执行,可以提高程序的执行效率。
例子:如果你在浏览器中打开多个标签页,浏览器进程中可能有多个线程在分别加载和显示不同的网页内容。
线程、程序、进程的之间关系是 什么 吗
程序与进程:
- 程序是静态的代码,进程是程序的一个实例,程序的执行需要通过进程来体现。也就是说,每当操作系统启动一个程序时,它就创建了一个进程,进程就是程序的运行时实例。
- 例如,启动一个文本编辑器程序(如 Notepad),操作系统会为它创建一个进程。
进程与线程:
- 进程是系统资源分配的基本单位,而线程是进程中的最小执行单位。每个进程至少有一个线程,通常称为主线程。除此之外,进程还可以创建多个子线程来并发执行任务。
- 多线程可以提高程序的执行效率。例如,一个视频播放器可能有一个线程负责播放视频,另一个线程负责处理用户输入(如暂停、播放按钮)。
程序、进程和线程的关系:
- 程序是静态的文件,进程是程序的动态执行实例,线程是进程内部执行的具体任务单位。
- 一个程序可以启动多个进程,一个进程内部可以有多个线程,多个线程共享该进程的内存资源。
我们可以用 QQ 这个例子来形象地说明 程序、进程 和 线程 之间的关系:
1. 程序:QQ 安装包
- 程序 就是你从网上下载的 QQ 安装包,比如
QQ_setup.exe
。它是一个静态的文件,里面存储了运行 QQ 所需的代码和资源。它本身没有任何动作,只是一个存储在硬盘上的文件。2. 进程:启动 QQ 后运行的 QQ 程序
当你双击安装包进行安装,或者直接点击已经安装好的 QQ 快捷方式时,操作系统会加载 程序,并启动一个 进程,这个进程就是你运行中的 QQ 客户端程序。
这个 进程 就是你在计算机中启动的一个实际的程序实例,操作系统为它分配了内存、CPU 时间等资源,并将其加载到内存中。每次你启动 QQ,操作系统就会创建一个独立的 进程 来运行它。
在你打开 QQ 时,进程就类似于 你打开了一个完整的 QQ 运行环境,它管理和维护着 QQ 所需的所有资源。
3. 线程:进程内部的任务
在 进程 内部,QQ 需要做很多事情,比如接收消息、发送消息、刷新聊天界面、检测网络状态等等。为了同时处理这些任务,进程 会创建多个 线程。
例如:
- 主线程:负责更新聊天界面、显示消息。
- 网络线程:负责与服务器进行通信,发送和接收数据。
- 后台线程:处理 QQ 的一些后台任务,比如检查新消息、下载更新等。
这些 线程 可以并发运行,互相协调工作。多个 线程 可以共享进程中的资源(如内存和文件句柄),这让 QQ 能够高效地处理多个任务。
总结关系
- 程序 是静态的,是 QQ 安装包文件,包含了所有的执行代码。
- 进程 是程序运行时的实例,它是运行中的 QQ 客户端,负责管理和调度资源。
- 线程 是进程内的执行单位,负责具体的任务(如接收消息、显示界面、检查网络等),并发执行提高了程序的效率。
java八股---java基础03(包、IO流、反射、String、包装类)-CSDN博客