0.什么是线程安全,怎么保证线程安全?
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
确保线程安全的方法主要有以下几种:
互斥锁(Mutex):使用锁机制来控制对共享资源的访问,确保同一时间只有一个线程可以访问该资源。
读写锁(Read-Write Lock):允许多个线程同时读取共享资源,但在写入时会阻塞其他线程的读取和写入。
线程局部存储(Thread-local Storage):每个线程都有自己的变量副本,避免了多个线程之间的共享。
不可变对象(Immutable Object):创建不可变对象,确保对象一旦创建后不能被修改,避免了并发修改带来的问题。
使用原子操作(Atomic Operations):一些数据结构和操作提供原子性,确保在执行时不会被其他线程干扰。
高层并发工具:使用现有的高层次并发库,如 Java 的
java.util.concurrent
包,提供了许多线程安全的集合和工具。
1.什么是Java的多态特性?
多态是指同一个接口或父类引用变量可以指向不同的对象实例,并根据实际指向的对象类型执行相应的方法。它允许同一方法在不同对象上表现出不同的行为,是面向对象编程(OOP)的核心特性之一。
1.1.多态的优点
通过多态,程序可以灵活地处理不同类型的对象,降低代码耦合度,增强系统的可扩展性。新增子类或实现类时,无需修改原有代码,只需通过接口或父类引用调用即可。
1.2.多态的意义
多态其实是一种抽象行为,它可以让程序员在抽象类的基础上实现具体的实现类,提高了编程的可扩展性。
代码示例:
class Person {
void work() {
System.out.println("工作");
}
}
class Student extends Person {
@Override
void work() {
System.out.println("上学");
}
}
public class Test {
public static void main(String[] args) {
Person person = new Student();
person.work(); // 输出 "上学"
}
}
个人理解助记:不同的人物角色在日常生活中的职责都不一样,如果每个角色写一个具体实现类的话不仅代码很多重复赘余,如果职责改变了需要修改代码也很麻烦。
注意混淆!!!!=>多重继承
Java是不支持多继承的,因为多继承会导致菱形继承(也叫钻石继承)问题,Java之父吸取了C++的教训,因此不支持多继承。
什么是菱形继承呢?
如图所示,BC继承了A,然后D继承了BC,假设此时要调用D内定义在A的方法,因为B和C都有不同的实现,此时就会出现歧义,不知道应该调用哪个了。
2.Vue的原理
vue.js是一个流行的前端框架,主要用于构建用户界面和单页应用。它的底层概念涉及如下几个关键概念:
2.1.响应式系统
Vue 的核心是其响应式系统。它使用了 Object.defineProperty(在 Vue 2 中)和 Proxy(在 Vue 3 中)来实现数据绑定(vue3中使用了Proxy代替了Object.defineProperty)。主要步骤包括:
-
数据劫持:当你定义一个 Vue 实例时,Vue 会遍历数据对象的属性,并使用
Object.defineProperty
或Proxy
创建 getter 和 setter。这些 getter 和 setter 允许 Vue 监听数据的变化。 -
依赖收集:当组件访问某个数据属性时,Vue 会收集这个属性的依赖(即所有依赖于这个属性的组件)。这通常是在 getter 中进行的。
-
触发更新:当数据发生变化时,setter 会被触发,Vue 会通知所有依赖这个数据的组件进行重新渲染。
2.2. 虚拟 DOM
Vue 使用虚拟 DOM 来优化性能。虚拟 DOM 是对真实 DOM 的一种抽象表示,它通过 JavaScript 对象的形式存储。
-
渲染:当组件需要渲染时,Vue 会创建一个虚拟 DOM 树。
-
Diff 算法(vue3引入):当数据变化时,Vue 会生成新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行比较。这种比较的过程称为 diff。它会找出哪些部分发生了变化,然后只更新那些部分,而不是重新渲染整个 DOM。
2.3. 组件化
Vue 采用了组件化的设计思想。每个 Vue 组件都有自己的模板、逻辑和样式,使得开发过程更加模块化和可维护。
-
组件生命周期:每个组件都有一系列的生命周期钩子,如
created
、mounted
、updated
和destroyed
,允许开发者在特定的时间点执行代码。 -
插槽:组件可以使用插槽(slots)来实现更灵活的内容分发,让父组件能够传递内容到子组件中。
2.4. 模板编译
Vue 使用模板语法来定义组件的 UI,模板在运行时被编译成渲染函数。
-
指令:Vue 提供了一些内置指令(如
v-if
、v-for
、v-bind
等),这些指令在编译过程中被转化为相应的渲染逻辑。 -
插值:模板中的数据插值(如
{{ message }}
)在编译后会被转化为相应的 JavaScript 表达式。
2.5. Vue Router 和 Vuex
-
Vue Router:用于处理路由的管理,使得单页应用能够在不同的视图间导航。
-
Vuex:是一个状态管理模式,用于在多个组件之间共享状态,使得应用的状态管理更加集中和可预测。
2.6. 插件机制
Vue 提供了一个强大的插件系统,可以通过 Vue.use()
来注册插件。插件可以添加全局功能,如全局组件、指令或混入等。
3.JVM的参数
- -Xmx:用于设置JVM的最大堆内存大小。例如,"-Xmx512m"表示将最大堆内存设置为512MB
- -Xms:用于设置JVM的初始堆内存大小。例如,"-Xms256m"表示初始堆内存为256MB
- -Xss:用于设置每个线程的堆栈大小。例如,"-Xss1m"表示将堆栈大小设置为1MB。.
- -Xmn:用于设置年轻代的堆内存大小。例如,"-Xmn256m"表示将年轻代的堆内存设置为256MB.
- -XX:PermSize:用于设置永久代的初始大小。在Java 8及之后的版本中,永久代已被元空间(Metaspace)取代。
- -XX:MaxPermSize:用于设置永久代的最大大小。在Java 8及之后的版本中,永久代已被元空间(Metaspace)取代。
- -XX:MaxMetaspaceSize:用于设置元空间的最大大小。在Java 8及之后的版本中,用于控制元空间大小。
- -XX:NewRatio:用于设置年轻代与老年代的内存比例。例如,“-XX:NewRatio=2"表示年轻代占整个堆内存的1/3,老年代占2/3。
- -XX:MaxTenuringThreshold:用于设置对象在年轻代中经历多少次垃圾回收后晋升到老年代。默认值通常为15。
- -XXSurvivorRatio:用于设置Eden区和Survivor区的内存比例。例如,"-XKSurvivorRatio=8"表示Eden区占整个年轻代的8/10,每个Survivor区占1/10。
- -XX:+UseConcMarkSweepGC:启用CMS (Concurrent Mark-Sweep)垃圾回收器。
- -XX:+UseG1GC:启用G1 (Garbage-First)垃圾回收器。
- -XX:MaxGCPauseMillis:用于设置垃圾回收的最大暂停时间目标。
- -XX:ParallelGCThreads:用于设置并行垃圾回收线程的数量。
- -XX:+PrintGCDetails:打印垃圾回收详细信息。
3.1. 为什么要进行JVM参数调优?
对 JVM 参数进行调优的目的是为了提高 Java 应用程序的性能和稳定性,避免内存不足、垃圾回收(GC)频繁导致的性能下降等问题。合理地配置 JVM 参数,可以充分利用系统资源、优化内存分配、减少垃圾回收的开销,进而提升应用的响应速度和处理能力。
4.什么是反射机制,说说你的理解?
Java的反射机制是一种强大的特性,允许程序在运行时查询和操作类的属性和方法。通过反射,程序可以动态地加载类、创建对象、调用方法、访问字段等。
4.1.反射机制的主要特点:
- 动态性:可以在运行时决定使用哪个类或方法,而不需要在编译时确定。
- 灵活性:可以访问私有成员、调用私有方法,打破了封装的限制。
- 通用性:适用于不确定具体类的情况,比如通过配置文件、注解等动态创建对象。
4.2.使用反射机制的场景:
- 框架设计:许多框架(如Spring、Hibernate)使用反射来实现依赖注入、对象持久化等功能。
- 插件系统:需要动态加载和调用不同的类,便于扩展和维护。
- 开发工具:如调试工具、测试框架,可以使用反射来检查对象状态、调用方法等。
- 序列化与反序列化:在将对象转化为字节流或从字节流重建对象时,可以使用反射来访问对象的字段。
4.3.注意事项:
- 反射会带来一定的性能开销,因为它需要在运行时进行类型检查。
- 使用反射访问私有成员可能导致代码难以维护,降低了封装性。
- 过度使用反射可能会导致代码的可读性和可理解性降低。
5.说说你对Java泛型的理解
5.1. 泛型的基本概念
泛型允许你在定义类、接口或方法时使用“参数化类型”,即将数据类型作为参数传递。它的核心思想是允许程序员编写的代码可以适用于各种数据类型,而不必重复编写相同的代码逻辑。
示例代码如下:
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
//使用泛型时可以指定类型
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String str = stringBox.getItem(); // 不需要强制类型转换
如果是普通的类的话,当你取出这个对象的时候需要进行强制类型转换,如果处理不当容易报错。
5.2. 泛型的通配符
Java 泛型支持通配符,用于表示不确定的类型,常用的通配符有 ?
、? extends T
、? super T
。
5.2.1 ?
通配符
?
表示未知的类型,它可以代表任何类型。常用于泛型方法和集合的参数。示例如下:
//此方法接受任何类型的list
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
5.2.2 ? extends T
上限通配符
? extends T
表示类型是 T
或 T
的子类型。常用于只读取数据的场景,保证数据类型的范围。
//该方法可以接受 Number 类型的 List,或其子类(如 Integer、Double)的 List。
public static void printNumbers(List<? extends Number> list) {
for (Number number : list) {
System.out.println(number);
}
}
5.2.3 ? super T
下限通配符
? super T
表示类型是 T
或 T
的超类型。常用于写入数据的场景,确保能够接受 T
类型或其父类的对象。
public static void addNumber(List<? super Integer> list) {
list.add(1); // 可以添加 Integer 类型的对象
}
5.3. 泛型的限制
尽管泛型非常强大,但在使用时也有一些限制:
- 基本类型不能作为泛型类型参数: 例如,
List<int>
是非法的。必须使用对应的包装类型List<Integer>
。 - 类型擦除: Java 的泛型在编译时会进行类型擦除,即所有的泛型信息在编译后都会被去除,这意味着泛型只在编译时有效,运行时无法获取泛型的具体类型。
5.4. 泛型和继承
在 Java 中,泛型类型之间没有继承关系。即使 Integer
是 Number
的子类,List<Integer>
并不是 List<Number>
的子类型。例如,不能把 List<Integer>
传递给期望 List<Number>
的方法。
错误示例:
List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers; // 编译错误
5.5. 总结作用
- 类型安全:泛型允许在编译时进行类型检查,确保在使用集合或其他泛型类时,不会出现类型不匹配的问题,减少了运行时的classcastException错误。
- 代码重用:泛型使代码可以适用于多种不同的类型,减少代码重复,提升可读性和维护性。
- 消除显式类型转换:泛型允许在编译时指定类型参数,从而消除了运行时需要显式类型转换的麻烦。
6.总结
非常糟糕的一次面试体验,快问快答式的面试,除了问到关于个人项目的时候回答出来了点东西,然后从问Java基础开始,连续问了十几道题,每道题超3秒没回答出来就跳过问下一道了,没给思考的时间,主要考察你对技术知识的掌握度,当时问的每个东西都感觉挺熟悉,但是就是几乎啥也没答上来,草草地就结束了这次的面试,当时忘记录音了,因为是国庆前的最后一个面试,面完之后整个人有点颓,有些问题记不清了,记起来再补充吧!