目录
本专栏全是博主自己收集的面试题,仅可参考,不能相信面试官就出这种题目。
一.为什么会有Java内存模型?
Java 内存模型存在的原因在于解决多线程环境下并发执行时的内存可见性和一致性问题,保证多线程并发操作的可见性、有序性和原子性。
Java内存模型内容:
- 主内存(Main Memory):所有线程共享的内存区域,包含了对象的字段、方法和运行时常量池等数据。
- 工作内存(Working Memory):每个线程拥有自己的工作内存,用于存储主内存中的数据的副本,线程只能直接操作工作内存中的数据。
- 内存间交互操作:线程通过读取和写入操作与主内存进行交互。读操作将数据从主内存复制到工作内存,写操作将修改后的数据刷新到主内存。
- 原子性(Atomicity):JMM 保证基本数据类型(如 int、long)的读写操作具有原子性,即不会被其他线程干扰,保证操作的完整性。
- 可见性(Visibility):JMM 确保一个线程对共享变量的修改对其他线程可见。这意味着一个线程在工作内存中修改了数据后,必须将最新的数据刷新到主内存,以便其他线程可以读取到更新后的数据。
- 有序性(Ordering):JMM 保证程序的执行顺序按照一定的规则进行,不会出现随机的重排序现象。这包括了编译器重排序、处理器重排序和内存重排序等。
二.什么样的情况下finally不会执行
正常运行的情况下,finally 中的代码是一定会执行的,但是在某些异常情况下,就不会执行。
1.程序在 try 块中遇到 System.exit() 方法,会立即终止程序的执行
java
public class FinallyExample {
public static void main(String[] args) {
try {
System.out.println("执行 try 代码.");
System.exit(0);
} finally {
System.out.println("执行 finally 代码.");
}
}
}
2.在 try 快中遇到 Runtime.getRuntime().halt() 代码,强制终止正在运行的 JVM。
java
public class FinallyExample {
public static void main(String[] args) {
try {
System.out.println("执行 try 代码.");
Runtime.getRuntime().halt(0);
} finally {
System.out.println("执行 finally 代码.");
}
}
}
3.程序在 try 块中遇到无限循环或者发生死锁等情况时,程序可能无法正常跳出 try 块,此时 finally 块中的代码也不会被执行。
4.编译器奔溃或者硬件故障(停电等)
三.钩子是什么?
在计算机编程中,"钩子"(hook)是一种技术或机制,允许程序在特定事件发生时插入自定义代码或逻辑。这种机制允许程序员在不修改程序源代码的情况下,改变或扩展程序的行为。
钩子类型:
-
关闭钩子(Shutdown Hooks):
- 在Java中,关闭钩子是一种特殊的钩子机制,允许开发者注册在JVM即将关闭之前执行的代码块。通过
Runtime.addShutdownHook(Thread hook)
方法可以注册一个线程,在JVM关闭时执行。这种机制通常用于释放资源、保存状态或执行清理操作,确保程序在退出前可以进行必要的收尾工作。
- 在Java中,关闭钩子是一种特殊的钩子机制,允许开发者注册在JVM即将关闭之前执行的代码块。通过
-
安装钩子:
- 在操作系统和网络编程中,"安装钩子"通常指在系统级别或特定应用程序中安装的一种回调机制。例如,在操作系统级别,可以安装键盘钩子来截获键盘输入事件,或者安装鼠标钩子来截获鼠标事件。这使得程序可以监视或修改输入事件的行为。
-
编程钩子:
- 在软件开发中,"编程钩子"指的是在代码中留下的一些特定点,允许开发者插入自定义代码。例如,一些框架或库可能会定义钩子函数(hook functions),允许用户根据需要扩展框架的功能或修改其行为。
四.编译时期的多态性和运行时期的多态性
编译时期的多态性:方法重载 ,通过静态绑定,在编译阶段就确定的具体方法,也称静态多态性。
运行时期的多态性:方法重写,依赖于动态绑定, 在子类覆盖(重写)父类的方法时。当通过父类引用指向子类对象,并调用被子类重写的方法时,实际执行的是子类中的方法。
java
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void makeSound() {
System.out.println("Dog barks");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // 父类引用指向子类对象
myAnimal.makeSound(); // 调用被子类重写的方法
}
}
五.谈谈反射机制
反射,可以反射一个对象内的所有方法,还可以获取所有的属性值。
通过反射,可以得到实例化对象,也可以使用该类的的所有方法(公共、私有)。
java
public class User {
public String name = "张三";
private int age = 18;
public void publicMethod() {
System.out.println("do public method");
}
private void privateMethod() {
System.out.println("do private method");
}
public static void staticMethod() {
System.out.println("do static method");
}
}
获取对象方法和属性:
java
// 1.反射得到对象
Class<?> clazz = Class.forName("User");
// 2.得到方法
Method method = clazz.getDeclaredMethod("publicMethod");
// 3.执行普通方法
method.invoke(clazz.getDeclaredConstructor().newInstance());
// 得到私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
// 设置私有方法可访问
privateMethod.setAccessible(true);
// 执行私有方法
privateMethod.invoke(clazz.getDeclaredConstructor().newInstance());
// 得到静态方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
// 执行静态方法
staticMethod.invoke(clazz);
// 得到公共属性
Field field = clazz.getDeclaredField("name");
// 得到属性值
String name = (String) field.get(clazz.getDeclaredConstructor().newInstance());
// 得到私有属性
Field privateField = clazz.getDeclaredField("age");
// 设置私有属性可访问
privateField.setAccessible(true);
// 得到属性值
int age = (int) privateField.get(clazz.getDeclaredConstructor().newInstance());
使用场景:
- 编程开发工具的代码提示
- Spring 中的依赖注入
- 数据库连接框架也会使用反射来实现调用不同类型的数据库
优缺点分析:
优点:灵活性、可扩展性;缺点:性能(获取类信息慢)、安全(会访问和修改类的方法和字段)
六.Java管道
或许有人没听说过,但是 所谓的管道 就是指:输入流和输出流之间的连接,用于在程序中传输数据。Java的管道可以用于在不同线程之间传递数据,或者用于对输入流进行处理后输出到输出流。
Java 管道的主要类和用法
1. PipedInputStream
和 PipedOutputStream
- PipedOutputStream :用于向管道发送字节数据。
- PipedInputStream :用于从管道接收字节数据。
java
import java.io.*;
public class PipeExample {
public static void main(String[] args) throws Exception {
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
Thread writerThread = new Thread(() -> {
try {
pos.write("Hello, Pipe!".getBytes());
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
});
Thread readerThread = new Thread(() -> {
try {
int data;
while ((data = pis.read()) != -1) {
System.out.print((char) data);
}
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
});
writerThread.start();
readerThread.start();
}
}
- 创建
PipedOutputStream
和PipedInputStream
实例,并将它们连接起来。 - 在
writerThread
中向PipedOutputStream
写入数据,最后关闭输出流。 - 在
readerThread
中从PipedInputStream
读取数据,并输出到控制台,直到读取结束后关闭输入流。
2. PipedReader
和 PipedWriter
- PipedWriter :用于向管道发送字符数据。
- PipedReader :用于从管道接收字符数据。
java
import java.io.*;
public class PipeExample {
public static void main(String[] args) throws Exception {
PipedWriter pw = new PipedWriter();
PipedReader pr = new PipedReader(pw);
Thread writerThread = new Thread(() -> {
try {
pw.write("Hello, Pipe!".toCharArray());
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
});
Thread readerThread = new Thread(() -> {
try {
int data;
while ((data = pr.read()) != -1) {
System.out.print((char) data);
}
pr.close();
} catch (IOException e) {
e.printStackTrace();
}
});
writerThread.start();
readerThread.start();
}
}
注意:
- 线程安全:Java的管道类提供了基本的线程安全性,可以在多线程环境下使用。
- 阻塞特性:当管道中没有数据可读时,读取操作会阻塞;当管道已满时,写入操作会阻塞。
- 关闭管道:在使用完毕后,应该及时关闭管道,以释放资源。