Java全栈面试题及答案汇总(2)

文章目录

51. @Component, @Controller, @Repository, @Service 有何区别?

通用注解,标记类为Spring管理的Bean,自动注册到容器,实例化Bean。

共同点

  • 均用于标识Spring管理的Bean
  • 可通过@ComponentScan自动扫描注册
  • 支持自定义名称(如@Service("userService"))

52. SpringBoot中的注解有哪些?SpringBoot的核心注解是什么?

常用注解

  1. @RestController:@Controller + @ResponseBody(返回JSON)。
  2. @ConfigurationProperties:批量注入配置(如@ConfigurationProperties(prefix="app"))。
  3. @Bean:在配置类中定义Bean(如@Bean public RedisTemplate redisTemplate())。
  4. @Value:注入单个配置值(如@Value("${app.name}"))。
  5. @SpringBootTest:标记测试类(集成测试)。
  6. @Conditional:条件化注册Bean(如@ConditionalOnClass)。
  7. @Profile:多环境配置(如@Profile("dev"))。

核心注解:@SpringBootApplication

组合注解,包含以下三个核心功能:

  1. @Configuration:标记配置类(替代XML配置)。
  2. @EnableAutoConfiguration:自动配置Bean(根据依赖jar包)。
  3. @ComponentScan:扫描当前包及子包的组件(@Controller、@Service等)。

53. springBoot要发布一个接口需要几个注解?

最少需要2个核心注解:

  1. @RestController:标记控制器并返回JSON
  2. @RequestMapping(或@GetMapping/@PostMapping):映射URL

示例

java 复制代码
@RestController // 1. 控制器注解
public class UserController {
    @GetMapping("/api/user") // 2. 请求映射注解
    public User getUser() {
        return new User("张三", 20);
    }
}

扩展注解(可选)

  • @RequestParam:获取请求参数
  • @PathVariable:获取URL路径参数
  • @RequestBody:接收JSON请求体
  • @ResponseStatus:设置响应状态码

54. SpringBoot的运行原理/启动流程?

  1. 启动类执行SpringApplication.run(Application.class, args)
  2. 初始化SpringApplication
    • 推断应用类型(Web/非Web)
    • 加载初始化器(Initializer)
    • 设置监听器(Listener)
  3. 准备环境:加载配置文件(application.properties/yaml)
  4. 创建上下文:根据应用类型创建WebApplicationContext
  5. 初始化上下文
    • 应用初始化器(ApplicationContextInitializer)
    • 注册BeanDefinition
    • 刷新上下文(refresh())
  6. 自动配置:@EnableAutoConfiguration根据classpath依赖生成Bean
  7. 启动完成:调用CommandLineRunner/ApplicationRunner接口

55. SpringBoot项目启动类上有什么注解?除了常用的还有什么注解?

核心注解

  • @SpringBootApplication:组合注解(@Configuration + @EnableAutoConfiguration + @ComponentScan)

其他可选注解

  1. @EnableAsync:启用异步方法支持
  2. @EnableScheduling:启用定时任务
  3. @EnableCaching:启用缓存机制
  4. @EnableTransactionManagement:启用事务管理
  5. @ComponentScan:自定义组件扫描范围
  6. @PropertySource:加载额外配置文件
  7. @Import:导入配置类
  8. @Profile:指定激活的环境

示例

java 复制代码
@SpringBootApplication
@EnableAsync
@EnableScheduling
@Profile("prod")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

56. SpringBoot配置文件怎么配?

配置文件类型

  1. application.properties

    properties 复制代码
    server.port=8080
    spring.datasource.url=jdbc:mysql://localhost:3306/db
  2. application.yml(推荐,结构化):

    yaml 复制代码
    server:
      port: 8080
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/db
        username: root
        password: 123456

多环境配置

  • 开发环境:application-dev.yml
  • 测试环境:application-test.yml
  • 生产环境:application-prod.yml

激活方式

  1. 配置文件指定spring.profiles.active=dev
  2. 命令行参数java -jar app.jar --spring.profiles.active=prod
  3. VM参数-Dspring.profiles.active=test

57. SpringBoot怎么整合MyBatis

整合步骤

  1. 添加依赖(pom.xml):

    xml 复制代码
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
  2. 配置数据源(application.yml):

    yaml 复制代码
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/test
        username: root
        password: 123456
    mybatis:
      mapper-locations: classpath:mapper/*.xml # Mapper.xml路径
      type-aliases-package: com.example.entity # 实体类包
  3. 创建实体类

    java 复制代码
    public class User {
        private Long id;
        private String name;
        // getter/setter
    }
  4. 创建Mapper接口

    java 复制代码
    @Mapper
    public interface UserMapper {
        User selectById(Long id);
    }
  5. 编写Mapper.xml

    xml 复制代码
    <mapper namespace="com.example.mapper.UserMapper">
        <select id="selectById" resultType="User">
            SELECT * FROM user WHERE id = #{id}
        </select>
    </mapper>
  6. Service层调用

    java 复制代码
    @Service
    public class UserService {
        @Autowired
        private UserMapper userMapper;
        
        public User getUser(Long id) {
            return userMapper.selectById(id);
        }
    }

58. SpringBoot项目启动时配置文件加载顺序

  1. 内部配置文件 (优先级从高到低):

    如果在不同的目录中存在多个配置文件,它的读取顺序是

    1、config/application.properties(项目根目录中config目录下)

    2、config/application.yml

    3、application.proprties (项目根目录下)

    4、application.yml

    5、resources/config/application.properties(项目resources目录中config目录下)

    6、 resources/config/application.yml

    7、resources/application.properties(项目的resources目录下)

    8、resources/application.yml

    如下图:

  2. 外部配置文件

    • 命令行参数(如--spring.config.location=file:/path/)
    • 环境变量(如SPRING_CONFIG_LOCATION)
    • 操作系统配置文件(如/etc/spring-boot/)
  3. 配置文件格式(优先级从高到低):

    • .properties
    • .yml
    • .yaml

59. 事务的隔离级别

数据库事务隔离级别

  1. 读未提交(Read Uncommitted)

    • 允许读取未提交的数据
    • 可能导致脏读、不可重复读、幻读
  2. 读已提交(Read Committed)

    • 只能读取已提交的数据
    • 解决脏读,仍可能不可重复读、幻读
    • Oracle默认级别
  3. 可重复读(Repeatable Read)

    • 事务期间多次读取同一数据结果一致
    • 解决脏读、不可重复读,仍可能幻读
    • MySQL默认级别(通过MVCC机制实现)
  4. 串行化(Serializable)

    • 最高隔离级别,事务串行执行
    • 解决所有并发问题,但性能极低

Spring事务隔离级别配置

java 复制代码
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void saveData() {
    // 业务逻辑
}

60. 事务的七种传播方式

Spring事务传播机制定义了多事务方法调用时的行为规则,共七种:

传播行为 说明
REQUIRED 默认。如果当前有事务则加入,否则新建事务。
SUPPORTS 支持当前事务,无事务则以非事务方式执行。
MANDATORY 必须在事务中执行,否则抛异常。
REQUIRES_NEW 新建事务,挂起当前事务(独立提交/回滚)。
NOT_SUPPORTED 以非事务方式执行,挂起当前事务。
NEVER 必须非事务执行,有事务则抛异常。
NESTED 嵌套事务(若当前有事务,创建保存点;无事务则同REQUIRED)。

示例:REQUIRES_NEW vs NESTED

  • REQUIRES_NEW:完全独立事务,外层回滚不影响内层提交。
  • NESTED:内层事务依赖外层事务,外层回滚内层也回滚,但内层回滚不影响外层。

61. 过滤器和拦截器的区别?

特性 过滤器(Filter) 拦截器(Interceptor)
技术依赖 Servlet规范 Spring框架
作用范围 所有Web请求(JSP/Servlet/静态资源) 仅Spring MVC请求(Controller方法)
执行时机 请求进入容器后,Servlet前 DispatcherServlet后,Controller前
实现方式 实现javax.servlet.Filter接口 实现HandlerInterceptor接口
方法参数 ServletRequest/ServletResponse HttpServletRequest/Response, HandlerMethod
生命周期 随Web容器启动/销毁 随Spring容器管理
功能 通用过滤(编码/登录验证) 业务拦截(权限/日志)

62. Spring运用了什么设计模式?讲一下哪些部分用到了这些设计模式

  • 工厂模式:BeanFactory、ApplicationContext创建Bean实例
  • 单例模式:默认Bean作用域(singleton)
  • 代理模式:AOP动态代理(JDK/CGLIB)
  • 模板方法:JdbcTemplate、RestTemplate
  • 观察者模式:事件监听机制(ApplicationEvent)
  • 策略模式:Resource加载策略、事务管理策略
  • 装饰器模式:BeanWrapper包装Bean对象
  • 适配器模式:HandlerAdapter处理请求适配

63. 去重的SQL语句怎么写?

1. 单字段去重查询

sql 复制代码
SELECT DISTINCT name FROM user;

2. 多字段去重查询

sql 复制代码
SELECT DISTINCT name, age FROM user; -- 所有字段组合去重

3. 删除重复记录(保留一条)

sql 复制代码
DELETE FROM user 
WHERE id NOT IN (
    SELECT MIN(id) FROM user 
    GROUP BY name, age -- 按重复字段分组
);

4. 窗口函数去重(保留最新记录)

sql 复制代码
WITH cte AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY create_time DESC) rn
    FROM user
)
SELECT * FROM cte WHERE rn = 1;

64. MYSQL大批量数据如何导入

1. 使用LOAD DATA INFILE(最快)

sql 复制代码
LOAD DATA INFILE '/path/to/data.csv'
INTO TABLE user
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS; -- 忽略表头

2. 使用mysqlimport命令行工具

bash 复制代码
mysqlimport -u root -p --fields-terminated-by=',' dbname user.csv

3. 优化导入性能

  • 禁用索引:ALTER TABLE user DISABLE KEYS; 导入后启用
  • 禁用事务:SET autocommit=0; 导入后提交
  • 增大max_allowed_packet参数
  • 使用批量插入(INSERT INTO ... VALUES (...),(...))

65. mysql如何做大数据量分页

优化前(慢查询)

sql 复制代码
SELECT * FROM user LIMIT 100000, 10; -- 偏移量大,全表扫描

优化方案

  1. 索引覆盖+子查询
sql 复制代码
SELECT * FROM user 
WHERE id >= (SELECT id FROM user LIMIT 100000, 1) 
LIMIT 10;
  1. 主键范围查询
sql 复制代码
SELECT * FROM user 
WHERE id BETWEEN 100000 AND 100010; -- 适用于连续ID
  1. 使用书签
sql 复制代码
-- 记录上一页最后一条ID
SELECT * FROM user 
WHERE id > 100000 
LIMIT 10;
  1. 分表查询:水平分表后,各表独立分页

66. mysql索引类型

1. 按数据结构分

  • B+树索引:最常用,支持范围查询(默认InnoDB索引)
  • Hash索引:仅Memory引擎支持,等值查询快,不支持范围查询
  • R树索引:空间索引,用于地理数据类型
  • Full-text索引:全文索引,支持文本搜索

2. 按功能分

  • 主键索引:PRIMARY KEY,唯一且非空
  • 唯一索引:UNIQUE,确保字段值唯一
  • 普通索引:INDEX,加速查询
  • 组合索引:多字段联合索引(遵循最左前缀原则)
  • 前缀索引:对字符串前缀建立索引(节省空间)

3. 按存储分

  • 聚簇索引:索引与数据存储在一起(InnoDB主键索引)
  • 非聚簇索引:索引与数据分离(InnoDB辅助索引)

67. 怎么避免索引失效

导致索引失效的常见情况

  1. 使用函数或运算WHERE SUBSTR(name,1,3)='abc'
  2. 隐式类型转换 :字符串不加引号WHERE phone=13800138000
  3. 否定操作符!=、<>、NOT IN、IS NOT NULL
  4. LIKE以%开头WHERE name LIKE '%abc'
  5. OR连接非索引列WHERE id=1 OR age=20(age无索引)
  6. 组合索引不满足最左前缀KEY (a,b,c)WHERE b=1 AND c=2
  7. MySQL优化器认为全表扫描更快:数据量少时

避免措施

  • 索引列避免函数/运算
  • 保证类型匹配
  • 使用覆盖索引
  • 拆分OR条件为UNION
  • 合理设计组合索引(最左前缀原则)

68. 了解过MySQL数据库储存引擎么

常见存储引擎

  1. InnoDB(MySQL 5.5+默认)

    • 支持事务(ACID)
    • 行级锁、外键约束
    • 聚簇索引,适合写密集型
    • 崩溃恢复能力强
  2. MyISAM

    • 不支持事务、行锁
    • 表级锁,读性能好
    • 全文索引支持
    • 适合读密集型(如博客)
  3. Memory

    • 数据存储在内存,速度快
    • 不支持持久化,重启数据丢失
    • 适合临时表、缓存
  4. Archive

    • 高压缩比,适合归档数据
    • 只支持INSERT/SELECT,不支持UPDATE/DELETE

选择建议

  • 事务需求:InnoDB
  • 只读查询:MyISAM
  • 临时数据:Memory
  • 归档数据:Archive

69. 讲讲mysql最左匹配原则

定义

最左前缀原则是指组合索引中,查询条件需从索引最左列开始匹配,否则索引失效。

示例(组合索引 KEY (a,b,c))

查询条件 索引使用情况
WHERE a=1 使用a列索引
WHERE a=1 AND b=2 使用a,b列索引
WHERE a=1 AND b=2 AND c=3 使用a,b,c列索引
WHERE b=2 不使用索引(违反最左原则)
WHERE a=1 AND c=3 仅使用a列索引
WHERE b=2 AND c=3 不使用索引

注意事项

  • 索引顺序与查询条件顺序无关(MySQL优化器会调整顺序)

  • 范围条件(>、<、BETWEEN)右侧列无法使用索引

    sql 复制代码
    WHERE a=1 AND b>2 AND c=3 -- 仅a,b列使用索引,c列失效

70. 笛卡尔积是什么

定义

笛卡尔积是指两个集合中所有可能的元素组合。在SQL中,当多表连接时未指定关联条件,会产生笛卡尔积。

示例

sql 复制代码
-- 表A(2行)
SELECT * FROM A; -- id: 1, 2

-- 表B(3行)
SELECT * FROM B; -- name: a, b, c

-- 笛卡尔积查询(无关联条件)
SELECT * FROM A, B; -- 结果:2×3=6行

危害

  • 数据量爆炸(n×m行)
  • 严重消耗CPU/内存
  • 查询效率极低

避免方式

  • 多表连接时必须指定关联条件(如WHERE A.id = B.a_id
  • 使用INNER JOIN显式连接

71. 脏读,幻读,不可重复读

事务并发问题

  1. 脏读

    • 事务A读取到事务B未提交的修改
    • 示例:A查询余额100 → B转账200未提交 → A查询余额300 → B回滚 → A读到脏数据
  2. 不可重复读

    • 事务A多次读取同一数据,事务B修改并提交,导致A读取结果不一致
    • 示例:A第一次查余额100 → B转账200提交 → A第二次查余额300
  3. 幻读

    • 事务A按条件查询,事务B插入满足条件的新数据,A再次查询出现新数据
    • 示例:A查询age>20的用户10人 → B插入age=25的用户 → A再次查询得11人

隔离级别解决

  • 读未提交:允许所有问题
  • 读已提交:解决脏读
  • 可重复读:解决脏读、不可重复读(MySQL默认)
  • 串行化:解决所有问题(性能低)

72. 数据库的优化方案?一个SQL语句如何让他搜索变快?你们遇到的数据量最大的表是什么样,有多少条数据。

数据库优化整体方案

  1. 索引优化:添加合适索引,避免索引失效
  2. SQL优化:避免全表扫描、减少JOIN、合理使用分页
  3. 表结构优化
    • 分库分表(水平/垂直拆分)
    • 字段类型优化(如用INT代替VARCHAR)
    • 适度冗余减少JOIN
  4. 配置优化
    • 调整连接数(max_connections)
    • 缓存设置(query_cache_size)
    • 日志优化(慢查询日志)
  5. 架构优化
    • 读写分离(主从复制)
    • 分库分表中间件(ShardingSphere)
    • 引入缓存(Redis)

SQL语句优化技巧

  1. 使用索引:WHEREJOIN字段加索引
  2. 避免SELECT *,只查需要字段(覆盖索引)
  3. 拆分大SQL为小SQL
  4. EXPLAIN分析执行计划
  5. 避免OR,改用UNION
  6. 批量操作代替循环单条操作

大数据量表处理经验

  • 最大表规模:电商订单表,约5000万行数据
  • 优化措施
    • 水平分表(按时间/用户ID哈希)
    • 历史数据归档(冷数据迁移至历史表)
    • 读写分离(主写从读)
    • 索引优化(仅保留必要索引)

73. 慢日志怎么使用?MYSQL怎么发现动作速度慢的SQL语句?

慢查询日志配置

  1. 开启慢日志

    ini 复制代码
    slow_query_log = ON
    slow_query_log_file = /var/log/mysql/slow.log
    long_query_time = 2 # 慢查询阈值(秒)
    log_queries_not_using_indexes = ON # 记录未使用索引的查询
  2. 查看慢日志

    bash 复制代码
    mysqldumpslow -s t /var/log/mysql/slow.log # 按时间排序

发现慢SQL的方法

  1. 慢查询日志:记录执行时间超过阈值的SQL

  2. SHOW PROCESSLIST:实时查看运行中的SQL

    sql 复制代码
    SHOW PROCESSLIST; -- 查看阻塞进程
  3. Performance Schema:详细监控SQL执行情况

    sql 复制代码
    SELECT * FROM performance_schema.events_statements_summary_by_digest 
    ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
  4. Explain分析

    sql 复制代码
    EXPLAIN SELECT * FROM user WHERE name = '张三';

74. 分库分表是怎么做的?数据库分表分库后怎么保证其一致性?

分库分表方案

1. 水平拆分(按数据行拆分)
  • 范围拆分:按时间(如订单表按月份拆分)
  • 哈希拆分:按用户ID哈希(如user_id % 8分8表)
  • 地理位置拆分:按区域拆分数据
2. 垂直拆分(按列拆分)
  • 按业务拆分:将大表拆分为用户表、订单表
  • 按冷热数据拆分:高频字段与低频字段分离

保证一致性措施

  1. 分布式事务
    • 两阶段提交(2PC)
    • TCC补偿事务
    • SAGA模式
  2. 最终一致性
    • 消息队列异步同步
    • 定时任务校验数据
  3. 全局ID
    • 雪花算法(Snowflake)
    • UUID保证唯一性
  4. 分布式锁:Redis/ZooKeeper实现跨库锁

75. MySQL表为何产生死锁?如何避免死锁?

死锁产生原因

  1. 循环等待资源:事务A持有资源1等待资源2,事务B持有资源2等待资源1
  2. 锁顺序不一致:不同事务加锁顺序相反
  3. 长事务:事务持有锁时间过长,增加冲突概率
  4. 高并发:大量事务同时竞争资源

死锁示例

sql 复制代码
-- 事务A
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 事务B
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 2;

-- 事务A继续
UPDATE account SET balance = balance + 100 WHERE id = 2; -- 等待B释放id=2的锁

-- 事务B继续
UPDATE account SET balance = balance + 100 WHERE id = 1; -- 等待A释放id=1的锁
-- 死锁产生!

避免死锁措施

  1. 统一加锁顺序:所有事务按固定顺序获取锁
  2. 控制事务大小:减少事务持有锁的时间
  3. 使用低隔离级别:如READ COMMITTED
  4. 添加合理索引:减少锁竞争范围
  5. 设置超时时间innodb_lock_wait_timeout
  6. 定期检测SHOW ENGINE INNODB STATUS查看死锁日志

76. oracle与mysql的区别?mysql分页和oracle分页的区别?

Oracle vs MySQL 核心区别

特性 Oracle MySQL
授权方式 商业收费 开源免费
事务隔离 读已提交(默认) 可重复读(默认)
锁机制 行级锁、表级锁 InnoDB行级锁,MyISAM表级锁
存储引擎 单一存储引擎 多存储引擎(InnoDB/MyISAM等)
语法差异 序列(SEQUENCE)、ROWNUM等 AUTO_INCREMENT、LIMIT等
备份恢复 RMAN工具 mysqldump、binlog

分页查询区别

MySQL分页(LIMIT)
sql 复制代码
SELECT * FROM user LIMIT 10 OFFSET 20; -- 从20行开始取10行
Oracle分页(ROWNUM)
sql 复制代码
-- 方式1:子查询
SELECT * FROM (
    SELECT t.*, ROWNUM rn FROM user t WHERE ROWNUM <= 30
) WHERE rn > 20;

-- 方式2:FETCH(12c+)
SELECT * FROM user OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

分页实现差异

  • MySQL使用LIMIT [offset,] rows,简单直观
  • Oracle需借助ROWNUM伪列或FETCH子句,语法复杂
  • 大数据量下,MySQL OFFSET过大会性能下降,Oracle ROWNUM需注意子查询顺序

77. Mysql默认的事务隔离级别?

MySQL默认事务隔离级别为可重复读(REPEATABLE READ)

特点

  • 保证同一事务多次读取同一数据结果一致
  • 通过MVCC(多版本并发控制)实现
  • 解决脏读、不可重复读问题
  • 可能存在幻读(通过间隙锁解决InnoDB的幻读)

查看与修改隔离级别

sql 复制代码
-- 查看当前隔离级别
SELECT @@tx_isolation;

-- 修改隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

78. Mysql的索引的数据结构?

MySQL主要使用B+树作为索引数据结构(InnoDB/MyISAM默认)。

B+树索引特点

  • 平衡树结构:所有叶子节点在同一层,查询效率稳定(O(log n))
  • 叶子节点链表:叶子节点间通过指针连接,支持范围查询
  • 聚簇索引:InnoDB主键索引叶子节点存储数据行
  • 非聚簇索引:叶子节点存储主键值,需回表查询数据

其他索引结构

  • Hash索引:仅Memory引擎支持,等值查询快,不支持范围查询
  • Full-text索引:倒排索引,用于全文搜索
  • R树索引:空间索引,用于地理数据类型

79. Mysql中count()和count(1)区别

功能区别

  • COUNT(*):统计所有行数,包括NULL值
  • COUNT(1):统计所有行数,忽略NULL值(与COUNT(*)功能类似)
  • COUNT(列名):统计非NULL值的行数

性能区别

  • InnoDB中,COUNT(*) 经过优化,性能最优(不实际取值,直接计数)
  • COUNT(1)性能略低于COUNT(*),需对每行计算常量1
  • COUNT(列名)性能最差,需判断列是否为NULL

使用建议

  • 统计总行数:优先使用COUNT(*)
  • 统计非空值:使用COUNT(列名)
  • COUNT(1)与COUNT()在InnoDB中性能差异不大,推荐使用COUNT()

80. Sql看执行计划?

使用EXPLAIN命令分析SQL执行计划,优化查询性能。

语法

sql 复制代码
EXPLAIN SELECT * FROM user WHERE name = '张三';

关键字段说明

  • type:访问类型(ALL全表扫描,ref索引查找,range范围扫描)
  • key:实际使用的索引
  • rows:预估扫描行数
  • Extra:额外信息(Using index覆盖索引,Using filesort文件排序)

优化依据

  • type为ALL时需添加索引
  • key为NULL表示未使用索引
  • Extra出现Using filesort/Using temporary需优化
  • rows值越小越好(接近实际行数)

81. 创建线程的方式及其区别

Java创建线程的四种方式

  1. 继承Thread类

    java 复制代码
    class MyThread extends Thread {
        public void run() { ... }
    }
    new MyThread().start();
  2. 实现Runnable接口

    java 复制代码
    class MyRunnable implements Runnable {
        public void run() { ... }
    }
    new Thread(new MyRunnable()).start();
  3. 实现Callable接口(有返回值):

    java 复制代码
    class MyCallable implements Callable<Integer> {
        public Integer call() { return 1; }
    }
    FutureTask<Integer> task = new FutureTask<>(new MyCallable());
    new Thread(task).start();
    Integer result = task.get(); // 获取返回值
  4. 线程池创建

    java 复制代码
    ExecutorService executor = Executors.newFixedThreadPool(5);
    executor.submit(new Runnable() {
        public void run() { ... }
    });
    executor.shutdown();

区别对比

方式 优点 缺点
Thread类 简单直接 单继承限制
Runnable接口 支持多实现 无返回值
Callable接口 有返回值、可抛异常 实现复杂
线程池 复用线程、控制资源 需要管理线程池生命周期

82. mysql锁的粒度?并发编程锁到行还是表?(介绍页级、表级、行级)

MySQL锁粒度

  1. 表级锁

    • 锁定整个表,开销小,并发低
    • MyISAM存储引擎默认锁
    • 适用:全表操作(如ALTER TABLE)
  2. 行级锁

    • 锁定单行记录,开销大,并发高
    • InnoDB存储引擎支持
    • 适用:高并发写操作
  3. 页级锁

    • 锁定一页数据(16KB),介于表级和行级之间
    • BDB存储引擎支持
    • 并发中等,开销中等

并发编程锁选择

  • 读多写少:表级锁(MyISAM)
  • 写多读少:行级锁(InnoDB)
  • 混合场景:InnoDB行级锁(默认)

InnoDB行级锁类型

  • 共享锁(S锁):读锁,多个事务可同时获取
  • 排他锁(X锁):写锁,独占锁定
  • 间隙锁:防止幻读,锁定范围区间

83. 什么是线程和进程?它们之间的区别是什么?

定义

  • 进程:操作系统资源分配的基本单位,拥有独立内存空间
  • 线程:进程内的执行单元,共享进程资源,CPU调度的基本单位

区别对比

特性 进程(Process) 线程(Thread)
资源分配 独立地址空间、内存、文件句柄 共享进程资源
切换开销 大(保存/恢复整个进程上下文) 小(仅保存线程上下文)
通信方式 复杂(IPC:管道/消息队列) 简单(共享内存/锁机制)
并发性 低(进程切换开销大) 高(线程切换开销小)
安全性 高(地址空间隔离) 低(共享资源需同步)
生命周期 独立,父进程结束不影响子进程 依赖进程,进程结束线程终止

84. 请解释一下线程的几种状态以及状态之间的转换关系

Java线程六种状态(Thread.State)

  1. NEW(新建):线程创建未启动
  2. RUNNABLE(可运行):就绪或运行中
  3. BLOCKED(阻塞):等待监视器锁(synchronized竞争)
  4. WAITING(等待):无超时等待(Object.wait()、Thread.join())
  5. TIMED_WAITING(计时等待):带超时等待(Thread.sleep()、Object.wait(1000))
  6. TERMINATED(终止):线程执行完毕

状态转换关系

  • NEW → RUNNABLE:调用start()方法
  • RUNNABLE → BLOCKED:获取锁失败
  • RUNNABLE → WAITING:调用wait()/join()无参方法
  • RUNNABLE → TIMED_WAITING:调用sleep(time)/wait(time)
  • BLOCKED/WAITING/TIMED_WAITING → RUNNABLE:获取锁/等待超时/被唤醒
  • RUNNABLE → TERMINATED:run()方法执行完毕

85. 什么是线程上下文切换?

定义

线程上下文切换是指CPU从一个线程切换到另一个线程执行时,保存当前线程状态并恢复新线程状态的过程。

切换内容

  • 程序计数器(下一条执行指令地址)
  • 寄存器值(通用寄存器、条件码等)
  • 栈指针(当前栈顶位置)

切换原因

  • 线程时间片用完(操作系统调度)
  • 线程阻塞(I/O、sleep()、wait())
  • 高优先级线程抢占
  • 线程主动让出CPU(yield())

影响

  • 上下文切换有性能开销(CPU周期消耗)
  • 频繁切换导致系统吞吐量下降
  • 优化方向:减少线程数量、减少阻塞操作、使用无锁并发

86. 项目中是否用到过多线程?怎么用的?

项目应用场景

  1. 异步任务处理

    • 订单支付后异步发送短信通知
    • 使用@Async注解或线程池提交任务
  2. 并发数据处理

    • 批量导入数据时多线程解析文件
    • 使用CompletableFuture并行处理任务
  3. 定时任务

    • 商品库存定时同步
    • 使用ScheduledExecutorService或Quartz
  4. 高性能服务

    • Netty服务器的I/O线程模型
    • Web服务器的请求处理线程池

代码示例(线程池处理异步任务)

java 复制代码
@Service
public class OrderService {
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    
    public void createOrder(Order order) {
        // 保存订单(同步)
        orderRepository.save(order);
        
        // 异步发送通知
        taskExecutor.execute(() -> {
            notificationService.sendSms(order.getUserId());
        });
    }
}

线程池配置

java 复制代码
@Configuration
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 核心线程数
        executor.setMaxPoolSize(10); // 最大线程数
        executor.setQueueCapacity(20); // 队列容量
        executor.initialize();
        return executor;
    }
}

87. Sleep()和wait()区别?Start()和run()区别?wait,sleep,notify的区别?

Sleep() vs wait()

特性 Thread.sleep() Object.wait()
所属类 Thread类静态方法 Object类实例方法
锁释放 不释放锁 释放对象锁
唤醒方式 超时自动唤醒 notify()/notifyAll()唤醒或超时
使用场景 暂停线程执行 线程间通信(等待/通知机制)
异常处理 需捕获InterruptedException 需捕获InterruptedException

Start() vs run()

特性 start() run()
作用 启动新线程,异步执行run() 普通方法调用,同步执行
线程状态 将线程从NEW转为RUNNABLE 不改变线程状态
调用次数 只能调用一次(多次抛异常) 可多次调用
底层实现 native方法,启动线程 普通Java方法

wait() vs notify()/notifyAll()

  • wait():使当前线程释放锁并进入等待状态
  • notify():随机唤醒一个等待该对象锁的线程
  • notifyAll():唤醒所有等待该对象锁的线程
  • 使用条件:必须在synchronized块中调用(获取对象锁后)

88. 线程安全指的是什么?如何保障线程安全

线程安全定义

多个线程并发访问时,无需额外同步操作,也能保证数据一致性和正确性。

线程不安全原因

  • 共享可变状态(如静态变量、实例变量)
  • 非原子操作(如i++)
  • 指令重排序

保障线程安全的措施

  1. 无状态设计:不使用共享变量
  2. 不可变对象:使用final关键字
  3. 同步机制
    • synchronized关键字(方法/代码块)
    • ReentrantLock显式锁
  4. 原子类:AtomicInteger、AtomicReference
  5. 并发容器:ConcurrentHashMap、CopyOnWriteArrayList
  6. 线程封闭:ThreadLocal变量
  7. volatile关键字:保证可见性,禁止指令重排序

89. 线程锁有几种方式?使用的关键字?

Java线程锁方式及关键字

  1. synchronized关键字

    • 同步方法public synchronized void method() {}
    • 同步代码块synchronized(obj) {}
    • 静态同步public static synchronized void staticMethod() {}
    • 底层:对象监视器锁(隐式获取/释放锁)
  2. ReentrantLock类

    java 复制代码
    Lock lock = new ReentrantLock();
    lock.lock();
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
    • 特性:可中断、可超时、可尝试获取锁、公平锁选项
  3. 读写锁(ReentrantReadWriteLock)

    java 复制代码
    ReadWriteLock rwLock = new ReentrantReadWriteLock();
    Lock readLock = rwLock.readLock(); // 读锁(共享)
    Lock writeLock = rwLock.writeLock(); // 写锁(排他)
  4. StampedLock:JDK 8+,乐观读模式提高并发性

  5. volatile关键字

    • 非锁机制,保证可见性和禁止指令重排序
    • 适用于单写多读场景

90. 什么是乐观锁和悲观锁?

悲观锁

  • 思想:假设并发冲突会发生,操作前先加锁
  • 实现:synchronized、ReentrantLock、数据库行锁
  • 适用场景:写冲突频繁(如库存扣减)
  • 优点:保证数据一致性
  • 缺点:阻塞等待,性能开销大

乐观锁

  • 思想:假设并发冲突不会发生,操作时检测冲突
  • 实现
    • 版本号机制(version字段)
    • CAS算法(Compare-And-Swap)
  • 适用场景:读多写少(如商品详情页)
  • 优点:无锁竞争,性能高
  • 缺点:ABA问题、自旋开销

实现示例

悲观锁(数据库行锁)
sql 复制代码
SELECT * FROM product WHERE id=1 FOR UPDATE; -- 获取行锁
UPDATE product SET stock=stock-1 WHERE id=1;
乐观锁(版本号)
sql 复制代码
UPDATE product 
SET stock=stock-1, version=version+1 
WHERE id=1 AND version=1; -- 条件不满足则更新失败

91. synchronized和lock的区别。Synchronize关键字修饰方法和修饰代码块的区别

synchronized vs Lock

特性 synchronized Lock(ReentrantLock)
获取方式 隐式获取(进入同步块) 显式调用lock()方法
释放方式 自动释放(退出同步块/异常) 手动释放(unlock(),需finally)
灵活性 低(不可中断、无超时) 高(可中断、可超时、可尝试获取)
性能 JDK 6+优化后与Lock接近 高并发下性能更优
功能扩展 可实现公平锁、条件变量(Condition)
底层实现 对象监视器(Monitor) AQS框架(AbstractQueuedSynchronizer)

synchronized修饰方法 vs 代码块

同步方法
java 复制代码
// 实例方法:锁对象为this
public synchronized void instanceMethod() {}

// 静态方法:锁对象为类.class
public static synchronized void staticMethod() {}
同步代码块
java 复制代码
// 锁对象为指定对象
synchronized(obj) {
    // 临界区
}

// 锁对象为this(等价于实例同步方法)
synchronized(this) {}

// 锁对象为类.class(等价于静态同步方法)
synchronized(MyClass.class) {}

区别

  • 锁粒度:代码块可精确控制锁范围(更小粒度)
  • 性能:代码块减少锁持有时间,提高并发性
  • 灵活性:代码块可指定任意锁对象,方法锁固定为this或类对象

92. 如何保证单例模式在多线程中的线程安全性

线程安全的单例实现方式

  1. 饿汉式

    java 复制代码
    public class EagerSingleton {
        private static final EagerSingleton instance = new EagerSingleton();
        private EagerSingleton() {}
        public static EagerSingleton getInstance() { return instance; }
    }
    • 类加载时初始化,天然线程安全
  2. 双重检查锁(DCL)

    java 复制代码
    public class LazySingleton {
        private volatile static LazySingleton instance;
        private LazySingleton() {}
        public static LazySingleton getInstance() {
            if (instance == null) { // 第一次检查
                synchronized (LazySingleton.class) {
                    if (instance == null) { // 第二次检查
                        instance = new LazySingleton();
                    }
                }
            }
            return instance;
        }
    }
    • volatile防止指令重排序,双重检查减少锁竞争
  3. 静态内部类

    java 复制代码
    public class HolderSingleton {
        private static class Holder {
            private static final HolderSingleton instance = new HolderSingleton();
        }
        public static HolderSingleton getInstance() {
            return Holder.instance;
        }
    }
    • 延迟初始化,类加载机制保证线程安全
  4. 枚举单例

    java 复制代码
    public enum EnumSingleton {
        INSTANCE;
        // 单例方法
        public void doSomething() {}
    }
    • 绝对线程安全,防反射和序列化攻击

93. 线程池的常见类型有那些?

Java线程池类型(Executors创建)

  1. FixedThreadPool:固定线程数的线程池

    java 复制代码
    ExecutorService fixedPool = Executors.newFixedThreadPool(5);
    • 核心线程数=最大线程数,无超时,队列无界
    • 适用:稳定并发场景
  2. CachedThreadPool:可缓存线程池

    java 复制代码
    ExecutorService cachedPool = Executors.newCachedThreadPool();
    • 核心线程数0,最大线程数Integer.MAX_VALUE,60秒超时
    • 适用:短期突发任务
  3. SingleThreadExecutor:单线程线程池

    java 复制代码
    ExecutorService singlePool = Executors.newSingleThreadExecutor();
    • 保证任务顺序执行,无并发
    • 适用:需串行执行的任务
  4. ScheduledThreadPool:定时任务线程池

    java 复制代码
    ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
    scheduledPool.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
    • 支持延迟执行、周期执行
    • 适用:定时任务、周期性任务

自定义线程池(推荐)

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, // 核心线程数
    10, // 最大线程数
    60, // 非核心线程超时时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100), // 任务队列
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

94. 线程池中线程的停止?

正确停止线程池的方法

  1. 优雅关闭(shutdown())

    java 复制代码
    executor.shutdown(); // 拒绝新任务,等待现有任务完成
  2. 立即关闭(shutdownNow())

    java 复制代码
    List<Runnable> unfinishedTasks = executor.shutdownNow(); // 尝试停止所有任务,返回未执行任务
  3. 等待关闭(awaitTermination())

    java 复制代码
    executor.shutdown();
    if (executor.awaitTermination(1, TimeUnit.MINUTES)) {
        // 正常关闭
    } else {
        // 超时处理
    }

停止线程的错误方式

  • 调用Thread.stop():已废弃,可能导致资源未释放
  • 使用volatile标记:需配合循环检查,非原子操作
  • interrupt()中断:需线程配合响应中断(检查isInterrupted())

线程协作停止

java 复制代码
class Task implements Runnable {
    private volatile boolean isStopped = false;
    
    public void stop() {
        isStopped = true;
    }
    
    public void run() {
        while (!isStopped && !Thread.currentThread().isInterrupted()) {
            // 任务逻辑
        }
    }
}

95. 线程池中的线程数多少合适?

线程数确定原则

CPU密集型任务
  • 公式:线程数 = CPU核心数 + 1
  • 原因:减少线程上下文切换,充分利用CPU
  • 示例:4核CPU → 5个线程
IO密集型任务
  • 公式:线程数 = CPU核心数 × 2 × (1 + IO耗时/CPU耗时)
  • 原因:线程等待IO时可切换执行其他任务
  • 示例:4核CPU,IO耗时:CPU耗时=10:1 → 4×2×(1+10)=88线程

实际调整策略

  1. 监控指标:CPU利用率、等待队列长度、任务完成时间
  2. 逐步调整:从理论值开始,根据性能测试优化
  3. 考虑因素
    • 内存限制(线程栈占用内存)
    • 任务类型混合(CPU+IO)
    • 第三方服务瓶颈(如数据库连接数)

工具推荐

  • 使用JDK自带工具:jstack、jconsole
  • 第三方监控:VisualVM、Arthas

96. 线程池一般是自动创建的还是手动?为什么?

推荐手动创建线程池

原因

  1. 避免资源耗尽风险

    • Executors默认线程池存在隐患:
      • FixedThreadPool/CachedThreadPool使用无界队列,可能堆积任务导致OOM
      • CachedThreadPool最大线程数为Integer.MAX_VALUE,可能创建过多线程
  2. 精细化控制

    • 手动配置核心参数:核心线程数、最大线程数、队列容量、拒绝策略
    • 根据业务场景定制(如IO密集型/CPU密集型)
  3. 明确拒绝策略

    • 自定义任务拒绝处理(如CallerRunsPolicy、AbortPolicy)

手动创建示例

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, // corePoolSize
    10, // maximumPoolSize
    60L, TimeUnit.SECONDS, // keepAliveTime
    new LinkedBlockingQueue<>(100), // workQueue
    Executors.defaultThreadFactory(), // threadFactory
    new ThreadPoolExecutor.CallerRunsPolicy() // handler
);

结论

生产环境中禁止使用Executors创建线程池,必须手动创建ThreadPoolExecutor,显式指定所有参数。

97. ReentrantLock和synchronized的区别是什么

核心区别

特性 ReentrantLock synchronized
锁获取方式 显式(lock()) 隐式(进入同步块)
锁释放方式 显式(unlock(),需finally) 自动释放
可中断性 可中断(lockInterruptibly()) 不可中断
超时获取 支持(tryLock(time)) 不支持
公平锁 可配置(构造参数fair=true) 非公平锁
条件变量 支持(Condition) 不支持
性能 高并发下更优 JDK 6+优化后接近Lock
底层实现 AQS框架 对象监视器(Monitor)

Condition条件变量示例

java 复制代码
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

// 生产者
lock.lock();
try {
    while (queue.size() == capacity) {
        notFull.await(); // 队列满则等待
    }
    queue.add(item);
    notEmpty.signal(); // 通知消费者
} finally {
    lock.unlock();
}

使用场景选择

  • 简单同步:使用synchronized(简洁、不易出错)
  • 高级功能(超时、中断、条件变量):使用ReentrantLock
  • 高并发读多写少:考虑ReentrantReadWriteLock

98. 什么是死锁?如何避免死锁?

死锁定义

多个线程因竞争资源而造成的互相等待的僵局,若无外力作用,线程将永远阻塞。

死锁必要条件

  1. 互斥条件:资源不可共享
  2. 请求与保持:持有部分资源并等待其他资源
  3. 不可剥夺:资源不可强行剥夺
  4. 循环等待:形成资源等待环路

死锁示例

java 复制代码
// 线程A
synchronized (lockA) {
    Thread.sleep(100);
    synchronized (lockB) { ... } // 等待lockB
}

// 线程B
synchronized (lockB) {
    Thread.sleep(100);
    synchronized (lockA) { ... } // 等待lockA
}

避免死锁措施

  1. 固定加锁顺序:所有线程按相同顺序获取锁
  2. 使用tryLock超时if (lock.tryLock(1, TimeUnit.SECONDS)) {}
  3. 减少锁持有时间:缩小同步块范围
  4. 使用无锁并发:原子类、ConcurrentHashMap
  5. 死锁检测
    • jstack命令查看线程状态
    • 编码时使用LockSupport检测

99. Java 中线程间通信有哪些方式

线程间通信方式

  1. 共享内存

    • 共享变量(需同步机制保证线程安全)
    • 示例:volatile boolean flag控制线程执行
  2. 等待/通知机制

    • Object.wait()/notify()/notifyAll()
    • Condition.await()/signal()
  3. 管道流

    • PipedInputStream/PipedOutputStream
    • 适用于线程间字节流通信
  4. Thread.join()

    • 等待另一个线程执行完毕
    java 复制代码
    Thread t = new Thread(() -> { ... });
    t.start();
    t.join(); // 等待t线程结束
  5. ThreadLocal

    • 线程私有变量,间接实现线程隔离
    • 适用于线程上下文传递(如用户会话)
  6. 并发工具类

    • CountDownLatch:计数器等待
    • CyclicBarrier:线程同步屏障
    • Semaphore:信号量控制并发数

等待/通知机制示例

java 复制代码
Object lock = new Object();

// 线程A等待
synchronized (lock) {
    while (condition) {
        lock.wait(); // 释放锁并等待
    }
}

// 线程B通知
synchronized (lock) {
    condition = false;
    lock.notify(); // 唤醒等待线程
}
相关推荐
@LetsTGBot搜索引擎机器人4 小时前
从零打造 Telegram 中文生态:界面汉化 + 中文Bot + @letstgbot 搜索引擎整合实战
开发语言·python·搜索引擎·github·全文检索
洲覆4 小时前
缓存异常:缓存穿透、缓存击穿、缓存雪崩
开发语言·数据库·mysql·缓存
Java爱好狂.4 小时前
接上篇:如何在项目中实现ES查询功能?
java·运维·jenkins·es·java面试·后端开发·java程序员
Iloveskr4 小时前
markdown转为pdf导出
java·pdf
逻极5 小时前
变量与可变性:Rust中的数据绑定
开发语言·后端·rust
一缕茶香思绪万堵5 小时前
028.爬虫专用浏览器-抓取#shadowRoot(closed)下
java·后端
Deamon Tree5 小时前
如何保证缓存与数据库更新时候的一致性
java·数据库·缓存
9号达人5 小时前
认证方案的设计与思考
java·后端·面试
大G的笔记本5 小时前
MySQL 中的 行锁(Record Lock) 和 间隙锁(Gap Lock)
java·数据库·mysql