线程有哪些基本状态?
New(新创建)
Runnable(可运行)
Blocked(被阻塞)
Waiting(等待)
Timed Waiting(计时等待)
Terminated(被终止)
当线程执行 wait()方法之后,线程进入 WAITING(等待)状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。
Java 中 IO 流分为几种?
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
BIO,NIO,AIO 有什么区别?
BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
深拷贝 vs 浅拷贝
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
如何在面试中-画龙点睛
final、finally、 finalize区别?
典型回答
final
final
是 Java 中的一个关键字,它可以用来修饰类、方法和变量,具有不同的含义:
final 修饰的类不能被继承。这意味着你不能从 final
类派生子类。这通常用于创建不可扩展的类,以确保类的完整性和封装性。
final 修饰的方法不能被子类重写(override)。这意味着即使子类继承了这个方法,也不能改变其行为。这有助于保护方法的实现不被修改,从而确保方法的行为始终如一。
final 修饰的变量是不可修改的。对于基本类型,这意味着一旦赋值就不可更改;对于引用类型,这意味着引用指向的对象不可更改,但对象内部的状态可以改变(除非对象本身也是 final
的)。
finally
finally
是 Java 异常处理机制的一部分,用于确保一段代码无论如何都会被执行。finally
块通常放在 try
块之后,可以紧跟在 catch
块之后,也可以独立存在。finally
块中的代码在 try
块执行完毕后总是会被执行,无论是否发生异常。这使得 finally
块非常适合用于释放资源,如关闭文件、数据库连接或解锁等。
finalize
finalize
是 java.lang.Object
类中的一个方法,用于在对象被垃圾回收之前执行清理工作。尽管 finalize
方法可以被重写以实现特定的资源清理逻辑,但这种方法现在已经被废弃,并且在 JDK 9 中被标记为 @Deprecated
。现代 Java 应用程序应该避免使用 finalize
方法,转而使用其他资源管理技术,如 try-with-resources
语句或显式的资源关闭机制。
考点分析
,面试官不单单是只是考察你对java基础知识掌握程度,可能考察你对性能、并发、对象生命周期或垃圾收集基本过程等方面的理解。
点睛
编程实践中充分利用final关键字来清晰表达代码的含义和逻辑目标,这一举措已在众多应用场景中展现出其优越性。
例如,通过将方法或类声明为final,开发者能够明确传达给其他团队成员及后续维护者,此类行为或功能是不可篡改的,从而确保了代码的稳定性和一致性。
深入研究Java的核心类库,我们会发现在java.lang包下,许多至关重要的类都被明智地声明为了final类。同样的现象在第三方类库的基础组件中也不鲜见,这样做有效地防止了API用户对核心功能的任意改动,从而在一定程度上加固了平台的安全性和可靠性。
此外,对于方法参数、局部变量乃至成员变量,适时使用final修饰符同样具有重要意义。这样做不仅可以明显减少因意外赋值引发的程序错误,某些编程规范甚至提倡将所有可能的情况都声明为final,以此强化代码的严谨性。
特别是在并发编程情境下,final变量具备了某种程度的不可变(immutable)特性,它能够很好地保护只读数据免受意外修改。由于final变量一旦初始化后不可再赋新值,所以在多线程环境下,程序员可以不必为final变量的同步操心,这无疑减轻了同步控制的负担,同时也规避了进行不必要的防御性复制操作,进而提升了代码的简洁性和效率。
finally块在Java编程实践中主要用于确保一段代码无论在何种情况下都能得到执行,特别是当代码块包含资源清理逻辑时,例如关闭数据库连接、网络套接字或者释放锁等操作。以下是finally实践中的几个重要注意事项:
异常处理: finally块常与try-catch一起使用,无论try块中是否抛出了异常,finally块中的代码都将被执行。这意味着无论异常是否被捕获或传播,finally总能完成资源的正确关闭或释放。
资源关闭的最佳实践:自从Java 7引入了try-with-resources语句后,对于实现了AutoCloseable接口的资源,如JDBC连接或文件流,更推荐使用try-with-resources来代替传统的try-finally结构,因为它能自动管理资源的关闭,减少了手动编写finally块的必要性,且代码更简洁。
避免副作用:在finally块中,应当避免对控制流产生意想不到的副作用,例如改变程序的执行路径或提前退出程序(如使用System.exit())。这是因为finally块的执行并不依赖于try或catch块中的逻辑,而是无条件执行的。
异常抑制:在finally块中抛出异常时,如果不处理这个异常,原来try或catch块中的异常将被这个新的异常所取代,原有异常信息可能会丢失。为了避免这种情况,通常不在finally中抛出新的异常,除非是有意要忽略之前的异常并报告更重要的错误。
代码执行顺序:finally块会在try和catch块执行完毕后立即执行,不受return、throw等语句的影响。但是在finally块执行完毕后,原先try或catch块中发生的异常(如果有的话)将继续向外传播。
总之,在使用finally时,应始终牢记其目的是确保资源的安全释放,同时尽可能减少对程序控制流和异常处理逻辑的干扰,以保持代码的清晰性和可读性。
在Java编程中,finalize
方法曾经被视为一种资源回收机制,允许对象在被垃圾收集器回收前执行最后一次清理操作。然而,由于finalize
方法存在诸多问题和不确定性,业界已经广泛认同其不是一个理想的资源管理策略,并在Java 9版本中将Object.finalize()
方法明确标注为过时(deprecated)。
finalize
方法不推荐使用的原因主要包括:
- 执行时间不确定:Java虚拟机(JVM)并不保证
finalize
方法何时会被调用,甚至可能永远不会被调用。这意味着依赖finalize
方法来清理重要资源极其不可靠。 - 性能影响严重:实现
finalize
方法的对象在垃圾收集过程中,会被当作特殊的对象处理,增加了GC的复杂度和延迟,可能导致性能大幅降低。 - 容易引发死锁和挂起:由于
finalize
方法的执行时机与垃圾回收紧密相连,它可能在不恰当的时候被触发,从而引发死锁或者其他并发问题。 - 不利于调试和错误处理:
finalize
方法中抛出的异常会被JVM默默吞掉,且无法保证其清理逻辑的执行效果,这对排查问题和确保资源释放极为不利。
相比之下,推荐使用更安全和高效的资源管理方式,如try-with-resources
语句,它可以确保在资源使用完毕后立即关闭,大大减少了资源泄漏的风险。另外,对于复杂的资源清理任务,可以利用Java提供的Cleaner
类,它通过PhantomReference实现了更为可控和安全的清理机制,相比finalize
更可靠和易于管理。
其它
关注公众号【 java程序猿技术】获取八股文系列文章