面试官:请简要介绍一下Java核心知识中的面向对象编程三大特性。
王铁牛:嗯,三大特性嘛,封装、继承、多态。封装就是把数据和操作数据的方法封装在一起;继承就是子类继承父类的属性和方法;多态就是同一个行为具有多个不同表现形式。
面试官:还不错。那说说在多线程中,如何保证线程安全?
王铁牛:可以用synchronized关键字,还有Lock接口,还有ThreadLocal,这些都能保证线程安全。
面试官:嗯,回答得还行。接下来问你几个关于JUC的问题。什么是CAS?
王铁牛:这个嘛,CAS就是比较并交换,通过比较内存值和预期值是否相等,相等就更新为新值。
第一轮结束。
面试官:下面进入第二轮。说说JVM的内存模型。
王铁牛:JVM内存模型包括堆、栈、方法区这些。堆是存放对象的,栈是存放局部变量的,方法区是存放类信息、常量等的。
面试官:那类加载机制的过程是怎样的?
王铁牛:类加载机制有加载、验证、准备、解析、初始化这几个过程。
面试官:如何排查JVM内存泄漏问题?
王铁牛:可以用工具,像jmap、jstack这些,来查看内存情况和线程堆栈信息。
第二轮结束。
面试官:最后一轮。讲讲线程池的核心参数。
王铁牛:核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
面试官:当提交的任务数大于线程池最大容量时会怎样?
王铁牛:这个,嗯,会放到队列里吧,如果队列也满了,就根据拒绝策略处理。
面试官:如何合理配置线程池参数?
王铁牛:这个得根据具体业务场景,比如任务类型、数量、执行时间等来配置。
面试结束,面试官表示会让王铁牛回家等通知。
答案:
- 面向对象编程三大特性:
- 封装:将数据和操作数据的方法封装在一起,对外提供统一的接口,隐藏内部实现细节,提高代码的安全性和可维护性。例如,在一个类中定义私有成员变量和公共的getter/setter方法来访问这些变量。
- 继承:子类继承父类的属性和方法,实现代码的复用。子类可以扩展父类的功能,同时保持与父类的一致性。比如,一个父类有基本的属性和方法,子类继承后可以添加自己特有的属性和方法。
- 多态:同一个行为具有多个不同表现形式。可以通过方法重写和接口实现来体现。比如,定义一个接口,多个类实现该接口,调用同一个方法时,会根据对象的实际类型执行不同的操作。
- 多线程中保证线程安全的方法:
- synchronized关键字:可以修饰代码块或方法,保证在同一时刻只有一个线程能访问被修饰的代码。例如,在一个方法上加上synchronized,当一个线程进入该方法时,其他线程需要等待。
- Lock接口:提供了比synchronized更灵活的锁控制。可以实现公平锁、可中断锁等。比如,使用ReentrantLock类来手动控制锁的获取和释放。
- ThreadLocal:为每个使用该变量的线程都提供一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。例如,在多线程环境下,每个线程都有自己独立的数据库连接。
- CAS(比较并交换):通过比较内存值和预期值是否相等,相等就更新为新值。它是一种无锁的原子操作,在多线程环境下可以高效地实现变量的原子更新。比如,在Java的Atomic类中广泛使用了CAS操作。
- JVM内存模型:
- 堆:存放对象实例,是垃圾回收的主要区域。分为新生代、老年代等。
- 栈:存放局部变量、方法调用等信息。每个线程都有自己独立的栈。
- 方法区:存放类信息、常量、静态变量等。
- 类加载机制的过程:
- 加载:将类的字节码文件加载到内存中,生成Class对象。
- 验证:检查加载的字节码文件是否符合JVM规范,确保安全性。
- 准备:为类的静态变量分配内存,并设置默认初始值。
- 解析:将符号引用转换为直接引用。
- 初始化:执行类的静态代码块,为静态变量赋真正的初始值。
- 排查JVM内存泄漏问题:
- jmap:可以生成堆转储快照,分析堆内存中的对象情况,找出可能导致内存泄漏的大对象或对象数量过多的情况。
- jstack:用于生成线程堆栈信息,通过分析线程的状态和调用栈,找出是否有死锁或长时间运行的线程导致内存无法释放。
- 线程池的核心参数:
- corePoolSize:线程池的核心线程数,当提交的任务数小于corePoolSize时,会创建新的线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数,当提交的任务数大于corePoolSize且workQueue已满时,会创建新的线程直到线程数达到maximumPoolSize。
- keepAliveTime:线程池中的线程在空闲时的存活时间,当线程空闲时间超过keepAliveTime时,线程会被销毁。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列,用于存放提交的任务,当线程数达到corePoolSize时,新提交的任务会放入workQueue中。
- threadFactory:线程工厂,用于创建线程,可自定义线程的名称、优先级等。
- handler:拒绝策略,当提交的任务数大于线程池最大容量且workQueue已满时,会调用handler来处理新提交的任务。
- 当提交的任务数大于线程池最大容量时的处理:当提交的任务数大于线程池最大容量时,首先会尝试将任务放入任务队列workQueue中,如果队列也已满,就会根据拒绝策略来处理。拒绝策略有AbortPolicy(抛出异常)、CallerRunsPolicy(调用者运行任务)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)等。
- 合理配置线程池参数:需要根据具体业务场景来配置。例如,如果任务类型是CPU密集型,线程池的corePoolSize可以设置为CPU核心数+1,以充分利用CPU资源;如果是I/O密集型任务,corePoolSize可以适当设置大一些。同时,要根据任务的数量和执行时间来合理调整maximumPoolSize和workQueue的大小。如果任务执行时间短且数量多,可以适当增大workQueue的容量;如果任务执行时间长,就需要控制maximumPoolSize,避免创建过多线程导致系统资源耗尽。