首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java面试题整理

Java面试题整理

原创
作者头像
用户11836118
修改2025-09-15 11:46:52
修改2025-09-15 11:46:52
870
举报

Java集合体系都有什么,都有什么区别与联系

Collection 是集合层次的根接口。

(1)List 接口 - 元素有序,允许存储重复元素,可通过索引访问元素。

主要实现类:

ArrayList:基于动态数组实现,查询快(随机访问 O(1)),增删慢(尤其是中间位置,O(n))。

非线程安全,适合查询多、增删少的场景。

LinkedList:基于双向链表实现,增删快(O(1),前提是知道节点位置),查询慢(O(n))。非线程安全。适合频繁增删的场景。

Vector:与 ArrayList 类似,但线程安全(方法用 synchronized 修饰),性能较低,不常用。

(2)Set 接口 - 无序、唯一

不允许存储重复元素(通过 equals() 和 hashCode() 判断)。无索引,不能通过下标访问。

主要实现类:

HashSet:基于 HashMap 实现。无序(不保证顺序)。查询、增删效率高(接近 O(1))。非线程安全。(其实就是

Set<String> studentNames = new HashSet<>();

studentNames.add("张三");)

LinkedHashSet:HashSet 的子类,内部使用链表维护插入顺序。有序(按插入顺序),性能略低于 HashSet。

(3)Map

存储 key-value 对。key 不允许重复(唯一),value 可以重复。通过 key 查找 value。

主要实现类:

HashMap:基于哈希表(数组 + 链表/红黑树)实现。无序。非线程安全。JDK 8 后,当链表长度 > 8 且数组长度 >= 64 时,链表转为红黑树,提高查找效率。

ConcurrentHashMap 本质上是一个HashMap,因此功能和HashMap一样,但是

ConcurrentHashMap 在 HashMap的基础上,提供了并发安全的实现。

典型使用场景:

场景举例:在 Web 应用中缓存用户信息、配置数据、热点商品信息等。

热点商品缓存示例(读多写少):

// 获取商品信息(高并发读),写请求:极少,只锁对应桶,不影响读。

Hashtable:早期的线程安全 Map 实现。不允许 null key 或 null value。

ArrayList 与 LinkedList 的区别

最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而

LinkedList 的底层数据结构是数据链表,不支持随机访问。

当哈希冲突发生时,HashMap使用链地址法来处理冲突。

线程问题:多线程,单线程,线程池

什么是线程 (Thread)?

定义:线程是进程中的一个执行单元,是 CPU 调度和执行的基本单位。一个进程可以包含多个线程。

类比:

进程 (Process):想象一个公司。它拥有独立的办公场所(内存空间)、资源(打印机、文件柜)和营业执照(独立的地址空间)。

线程 (Thread):公司里的员工。多个员工(线程)共享公司的资源(内存、文件句柄等),但他们可以同时工作(并发执行不同的任务)。

线程与进程的区别?

进程是操作系统分配资源的最小单元,线程是系统任务调度的最小单元。一个程序至少有一个进程,一个进程至少有一个线程。

什么是线程池? 为什么要使用它?

创建线程要花费昂贵的资源和时间,当任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候

就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

单线程:

一个程序在同一时刻只有一条执行路径(一个线程)在运行。

特点:

简单:编程模型简单,逻辑清晰,无需考虑并发、同步、死锁等问题。

顺序执行:任务必须一个接一个地完成。如果一个任务耗时很长(如网络请求、文件读写、复杂计算),整个程序就会“卡住”,无法响应其他操作。

资源利用率低:在等待 I/O 操作(如读磁盘、等网络响应)时,CPU 处于空闲状态,无法利用起来处理其他任务。

典型应用:

简单的命令行工具。

早期的 GUI 应用(容易卡顿)。

对性能和并发要求不高的脚本。

例子:你用一个单线程程序下载文件。在下载过程中,程序界面完全无法操作,直到下载完成。

多线程 (Multi-threading)

定义:一个程序在同一时间内创建并运行多个线程,这些线程可以并发(或并行)执行不同的任务。

目的:

提高性能和响应性:将耗时的任务(如 I/O、计算)放到后台线程执行,主线程(如 GUI 线程)可以保持对用户操作的响应。

充分利用多核 CPU:现代 CPU 通常是多核的。多线程可以让不同的线程在不同的 CPU 核心上真正并行执行,最大化 CPU 利用率。

模拟并发行为:如服务器需要同时处理成百上千个客户端请求。

特点:

并发性:多个线程看起来是“同时”运行的(在单核 CPU 上通过时间片轮转实现)。

并行性:在多核 CPU 上,多个线程可以真正在不同核心上同时运行。

复杂性:引入了线程安全、死锁、竞态条件、上下文切换开销等复杂问题。

什么是死锁:死锁 (Deadlock):两个或多个线程互相等待对方释放锁,导致所有线程都无法继续执行。

线程池 (Thread Pool)

为什么需要线程池?

直接创建线程(new Thread().start())虽然简单,但在高并发场景下有严重问题:

创建/销毁开销大:频繁创建和销毁线程本身就很耗时。

资源耗尽:无限制地创建线程会耗尽系统资源(内存、CPU),导致系统崩溃或性能急剧下降。

缺乏管理:难以控制并发线程的数量,无法复用线程。

定义:线程池是一种管理线程的机制。它预先创建一组可重用的线程(“池子”),并将需要执行的任务提交给这个池。池中的线程会循环地从任务队列中取出任务并执行,执行完后不销毁,而是等待下一个任务。

核心组件:

线程池 (ThreadPool):管理线程集合。

工作线程 (Worker Thread):池中实际执行任务的线程。

任务队列 (Task Queue / Blocking Queue):存放待执行任务的队列。当所有工作线程都在忙时,新任务会进入队列等待。

拒绝策略 (Rejected Execution Handler):当任务队列也满了,且线程数达到上限时,如何处理新提交的任务(如抛异常、丢弃、调用者线程执行等)。

何时使用线程池?

高并发的服务器应用(Web 服务器、数据库连接池)。

需要执行大量异步任务的程序。

任何需要避免频繁创建线程的场景。

创建线程的方式

方式一:继承 Thread 类,通过创建 Thread 类的子类,并重写其 run() 方法来创建线程。

方式二:实现 Runnable 接口,创建一个类实现 Runnable 接口,并实现其 run() 方法。

方式三:实现 Callable 接口 。生产环境、高并发、大量异步任务。

方式四:使用线程池。

在实际开发中,优先使用线程池 (ExecutorService) 配合 Runnable 或 Callable创建线程。

创建线程池的方式:

一、 使用 Executors 工厂类

1.Executors.newFixedThreadPool创建一个固定大小的线程池。创建一个固定大小的线程池。负载比较重、任务执行时间较长且稳定的场景。可以有效控制并发线程数。

2. Executors.newCachedThreadPool()创建一个可缓存的线程池。线程池大小不固定,线程可以按需创建。适用场景:执行大量短期异步任务的程序。

3. Executors.newSingleThreadExecutor()创建一个单线程的线程池。适用场景:需要保证任务按顺序执行,并且希望线程在意外终止后能自动恢复的场景。相当于一个“守护”线程。

二、 使用 ThreadPoolExecutor 构造函数创建线程池但阿里《Java开发手册》等规范建议避免使用它创建线程池,因为它隐藏了一些关键配置(如队列类型),容易导致风险(如无界队列)。

务必使用有界队列(如 ArrayBlockingQueue)来防止内存溢出。

如何确保线程安全

同步,使用原子类,实现并发锁,使用 volatile 关键字,使用线程安全类。

线程线程被创建后并不会立即执行

线程被创建后并不会立即执行,而是需要调用start()方法来启动线程。当调用start()方法后,线程会进入就绪状态,等待CPU分配时间片。

一旦获得就会开始执行其run()方法中的代码。

使用 start() 方法会启动一个新的线程,并且新线程会运行 run() 方法中的代码。

直接调用 run() 方法不会启动新的线程,只是在当前线程中执行 run() 方法中的代码。

线程堵塞

这种情况通常发生在多线程环境中,比如等待I/O操作完成、等待锁释放、等待条件变量等。

StringBuffer是线程安全的,因为它的大部分方法都是同步的,可以在多线程环境下安全使用。

而StringBuilder不是线程安全的,它没有同步机制,因此在多线程环境下会导致数据不一致的问题。

接口和抽象类问题

抽象类可以包含已经实现的方法,而接口通常只包含方法名。

https://cloud.tencent.com/developer/article/1677833

接口和抽象类都用于实现抽象化。

从Java 8开始,接口可以包含默认方法和静态方法,这些方法仍然是抽象的。

在Java中,子类通过extends关键字来继承抽象类,一个类只能继承一个抽象类,但可以实现多个接口。

Java中的反射:

反射是一种强大的工具,允许程序在运行时检查和修改其自身结构。反射的主要用途包括:

1动态创建对象2访问私有成员变量3获取类信息

通过反射创建对象有几种方式

1.使用Class对象的newInstance()方法

2.使用Constructor对象的newInstance()方法

3.通过ObjectInputStream的readObject方法实现反序列化

简要说一下springboot

Spring Boot 是一个基于Java的框架,它简化了基于Spring的应用程序的初始设置和开发过程。

实现了自动配置,无需进行复杂的XML配置即可快速进行开发工作。它通过注解的方式自动配置Bean。

提供了许多“起步依赖”,预先配置好的一组依赖项,可以快速开始项目。

Spring Boot遵循“约定优于配置”的原则。

Spring Boot支持内嵌的Tomcat服务器。

提供命令行工具,使用脚本编写和运行Spring应用。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java集合体系都有什么,都有什么区别与联系
    • Collection 是集合层次的根接口。
    • ArrayList 与 LinkedList 的区别
  • 线程问题:多线程,单线程,线程池
    • 什么是线程 (Thread)?
    • 线程与进程的区别?
    • 什么是线程池? 为什么要使用它?
    • 单线程:
    • 多线程 (Multi-threading)
    • 线程池 (Thread Pool)
      • 为什么需要线程池?
      • 何时使用线程池?
      • 创建线程的方式
      • 创建线程池的方式:
    • 如何确保线程安全
    • 线程线程被创建后并不会立即执行
  • 接口和抽象类问题
    • 抽象类可以包含已经实现的方法,而接口通常只包含方法名。
    • 接口和抽象类都用于实现抽象化。
  • Java中的反射:
  • 简要说一下springboot
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档