이것은 가장 어려운 Java 면접 질문 10가지 목록입니다. 이러한 질문은 주로 Java의 핵심 부분에서 나오며 Java EE 관련 문제와 관련이 없습니다. 이러한 어려운 Java 질문에 대한 답을 알고 있을 수도 있고 Java 지식에 대한 도전이 충분하지 않다고 느낄 수도 있습니다. 그러나 이러한 질문은 다양한 Java 인터뷰에서 쉽게 묻는 질문이며 내 친구 및 동료 프로그래머를 포함한 많은 프로그래머가 어려움을 겪습니다. 답변.
(더 많은 면접 질문 추천: java 면접 질문 및 답변)
어려운 Java 질문입니다. Java 프로그래밍 언어를 직접 설계하지 않았다면 어떻게 이 질문에 대답할 수 있습니까? Java 프로그래밍에 대한 일반적인 지식과 심층적인 이해는 이 어려운 핵심 Java 인터뷰 질문에 답하는 데 도움이 됩니다.
기다림, 알림, 알림이 모두 Thread 클래스가 아닌 Object 클래스에 정의되어 있는 이유
2~4년 경력의 Java 선배 개발자들이 물어볼 수 있는 유명한 Java 인터뷰 질문입니다.
이 질문의 좋은 점은 대기 알림 메커니즘에 대한 면접관의 이해와 이 주제에 대한 이해가 명확한지 여부를 반영한다는 것입니다. Java에서 다중 상속이 지원되지 않는 이유나 Java에서 String이 final인 이유에 대한 질문과 마찬가지로 이 질문에도 여러 가지 답변이 있을 수 있습니다.
대기 및 알림 메서드가 Object 클래스에 정의된 몇 가지 이유는 누구나 알 수 있습니다. 내 인터뷰 경험으로 볼 때, wait와 nofity는 대부분의 Java 프로그래머, 특히 2~3년의 경험을 가진 개발자에게 여전히 가장 혼란스러운 단어입니다. 따라서 Java 인터뷰를 하러 간다면 대기 및 알림 메커니즘을 잘 이해하고 대기를 사용하여 코드를 쉽게 작성할 수 있는지 확인하고 생산자-소비자 문제 또는 차단 대기열 구현 등을 통한 알림 메커니즘을 이해해야 합니다.
동기화된 블록이나 메소드에서 wait 및 inform을 호출해야 하는 이유와 Java의 wait, sleep 및yield 메소드 간의 차이점을 아직 읽지 않았다면 읽어보면 흥미로울 것입니다. wait, inform 및 informAll이 Object 클래스에 속하면 안 되는 이유는 무엇입니까? 제가 생각하기에 타당하다고 생각되는 몇 가지 아이디어는 다음과 같습니다.
1) wait 및 inform은 일반적인 메서드나 동기화 도구가 아니라 그 이상입니다. 중요한 것은 Java의 두 스레드 간의 통신 메커니즘입니다. 언어 디자이너의 경우 이 통신 메커니즘을 Java 키워드(예: 동기화)를 통해 구현할 수 없고 동시에 이 메커니즘을 모든 객체에 사용할 수 있도록 보장하는 경우 Object 클래스가 이를 선언하는 올바른 위치입니다. 동기화와 알림 대기는 서로 다른 영역이므로 동일하거나 관련된 것으로 취급하지 마십시오. 동기화는 상호 배제를 제공하고 Java 클래스의 스레드 안전성을 보장하는 반면 대기 및 알림은 두 스레드 간의 통신 메커니즘입니다.
2) 모든 객체는 잠글 수 있습니다. 이는 Thread 클래스 대신 Object 클래스에서 대기 및 알림을 선언하는 또 다른 이유입니다.
3) Java에서 코드의 중요한 섹션에 들어가려면 스레드를 잠그고 잠금을 기다려야 합니다. 어떤 스레드가 잠금을 보유하고 있는지는 모르지만 잠금이 스레드에 의해 보유된다는 것만 알 수 있습니다. 특정 스레드는 동기화된 블록 내부에 어떤 스레드가 있는지 파악하고 잠금 해제를 요청하는 대신 잠금을 얻기 위해 기다려야 합니다.
4) Java는 Hoare의 모니터 아이디어를 기반으로 합니다. Java에서는 모든 객체에 모니터가 있습니다.
스레드는 모니터에서 대기합니다.
스레드
모니터(모든 객체)
Java 설계에서는 스레드를 지정할 수 없습니다. 항상 현재 코드를 실행하는 스레드입니다. 그러나 모니터를 지정할 수 있습니다(이것을 대기 개체라고 합니다). 원하는 모니터에서 대기 중인 다른 스레드가 있을 수 있다면 동시 프로그램을 설계할 때 어려움을 야기하는 "침입"으로 이어질 수 있기 때문에 이는 좋은 설계입니다. Java에서는 다른 스레드의 실행을 방해하는 모든 작업(예: stop 메소드)이 더 이상 사용되지 않습니다.
이 Java 핵심 질문은 귀하의 답변이 면접관을 만족시키지 못할 수 있기 때문에 답변하기 어렵다고 생각합니다. 대부분의 경우 면접관은 답변의 핵심 사항을 찾고 있으며 이러한 핵심 사항을 언급하면 면접관은 기뻐할 것입니다. Java에서 이와 같은 어려운 질문에 답하는 열쇠는 다음에 나올 수 있는 다양한 질문을 처리하기 위한 관련 주제를 준비하는 것입니다.
이것은 Java에서 문자열이 불변인 이유와 매우 유사한 매우 고전적인 질문입니다. 이 두 질문의 유사점은 주로 Java 작성자의 디자인 결정 때문이라는 것입니다.
Java가 다중 상속을 지원하지 않는 이유는 다음 두 가지 사항을 고려해 볼 수 있습니다.
1) 첫 번째 이유는 다이아몬드 상속 문제를 둘러싼 모호함입니다. foo() 메서드가 있는 클래스 A를 고려하면 B와 C는 A에서 파생되고 자체 foo() 구현이 있습니다. 이제 클래스 D는 B와 C에서 파생된 다중 상속을 사용하면 foo()만 참조하면 컴파일러는 어떤 foo()를 호출해야 할지 결정할 수 없습니다. 이 상속 방식의 구조가 다이아몬드와 유사하기 때문에 이를 다이아몬드 문제라고도 합니다. 아래 이미지를 참조하세요.
A foo() / \ / \ foo() B C foo() \ / \ / D foo()
다이아몬드의 상위 A 클래스를 제거하고 다중 상속을 허용하더라도 모호한 측면이 보입니다. 이 문제의. 면접관에게 이 이유를 말하면 그는 왜 C++은 다중 상속을 지원하지만 Java는 지원하지 못하느냐고 물을 것입니다. 글쎄, 이 경우 나는 그에게 아래에 제시한 두 번째 이유를 설명하려고 합니다. 기술적인 어려움 때문이 아니라 더 많은 유지 관리 가능성과 더 명확한 디자인이 원동력 요소입니다. 이는 Java 언어 디자이너에 의해서만 확인될 수 있습니다. 추측하고 있을 뿐입니다. Wikipedia 링크에는 다중 상속을 사용할 때 다이아몬드 문제로 인해 다른 언어 주소 문제가 어떻게 발생하는지에 대한 좋은 설명이 있습니다.
2) 제가 생각하는 두 번째로 더 설득력 있는 이유는 다중 상속으로 인해 설계가 복잡해지고 변환, 생성자 연결 등의 과정에서 문제가 발생한다는 것입니다. 다중 상속이 필요한 상황이 많지 않다고 가정하면 단순화를 위해 이를 생략하는 것이 현명한 결정입니다. 또한 Java는 인터페이스를 사용하여 단일 상속을 지원함으로써 이러한 모호성을 피할 수 있습니다. 인터페이스에는 메소드 선언만 있고 구현을 제공하지 않으므로 특정 메소드에 대한 구현이 하나만 있으므로 모호성은 없습니다. (자바 면접 질문의 실용적이고 자세한 내용은 자바 지인 공식 계정의 "면접 질문 모음"에 답변을 남겨주시면 됩니다.)
또 다른 비슷한 까다로운 Java 질문입니다. C++에서는 연산자 오버로드를 지원하지만 Java에서는 지원하지 않는 이유는 무엇입니까? 어떤 사람들은 문자열 연결을 위해 Java에서 + 연산자가 오버로드되었다고 말할 수 있습니다. 이러한 주장에 속지 마십시오.
C++와 달리 Java는 연산자 오버로드를 지원하지 않습니다. Java는 프로그래머에게 +, -, * 및 / 등과 같은 표준 산술 연산자의 무료 오버로딩을 제공하지 않습니다. 이전에 C++를 사용한 적이 있다면 Java는 C++에 비해 많은 기능이 부족합니다. 예를 들어 Java는 다중 상속을 지원하지 않으며 Java에는 포인터가 없으며 Java에는 참조 전달이 없습니다. 또 다른 유사한 질문은 Java 참조에 의한 전달에 관한 것입니다. 이는 Java가 매개변수를 값으로 전달하는지 아니면 참조로 전달하는지를 주로 보여줍니다. 비록 그 뒤에 숨은 진짜 이유는 모르지만, Java가 연산자 오버로딩을 지원하지 않는 이유에 대한 다음 설명에는 어느 정도 진실이 있다고 생각합니다.
1) 단순성과 명확성. 명확성은 Java 디자이너의 목표 중 하나입니다. 디자이너들은 단순히 언어를 복사하는 것이 아니라 명확하고 진정한 객체 지향 언어를 원했습니다. 연산자 오버로딩을 추가하면 없는 것보다 확실히 디자인이 더 복잡해지고, 연산자가 실제로 의미하는 바를 식별하기 위한 추가 작업이 필요하고 최적화 기회가 줄어들기 때문에 컴파일러가 더 복잡해지거나 JVM 속도가 느려질 수 있습니다. Java의 연산자 동작.
2) 프로그래밍 오류를 피하세요. Java에서는 사용자 정의 연산자 오버로드를 허용하지 않습니다. 왜냐하면 프로그래머가 연산자 오버로드를 허용하면 동일한 연산자에 여러 의미가 부여되어 개발자의 학습 곡선이 가파르고 상황이 더욱 혼란스러워지기 때문입니다. 언어가 연산자 오버로드를 지원하면 프로그래밍 오류가 증가하여 개발 및 전달 시간이 늘어나는 것으로 관찰되었습니다. Java와 JVM은 이미 가비지 컬렉터를 제공하여 메모리 관리와 관련하여 개발자의 책임 대부분을 담당하고 있으므로 이 기능이 코드를 오염시키고 프로그래밍 오류의 원인이 될 가능성을 높이므로 별 의미가 없습니다.
3) JVM 복잡성. JVM 관점에서 연산자 오버로드를 지원하면 문제가 더 어려워집니다. 메소드 오버로딩을 사용하면 더 직관적이고 깔끔한 방법으로 동일한 결과를 얻을 수 있으므로 Java에서 연산자 오버로딩을 지원하지 않는 것이 합리적입니다. 상대적으로 단순한 JVM에 비해 복잡한 JVM은 JVM 속도를 늦추고 Java에서 결정적인 연산자 동작을 보장하기 위해 코드를 최적화할 기회가 적을 수 있습니다.
4) 개발 도구를 다루기 쉽게 만듭니다. 이것은 Java에서 연산자 오버로드를 지원하지 않는 또 다른 이점입니다. 연산자 오버로딩을 생략하면 언어 작업이 더 쉬워지고, 결과적으로 IDE나 리팩터링 도구와 같은 언어 작업을 위한 도구를 더 쉽게 개발할 수 있습니다. 리팩토링 도구는 C++보다 Java에서 훨씬 더 좋습니다.
내가 가장 좋아하는 Java 인터뷰 질문은 까다롭지만 매우 유용합니다. 일부 면접관은 종종 Java에서 String이 왜 final인지 묻는 질문을 합니다.
String 객체는 String 풀에 캐시되기 때문에 Java에서는 문자열을 변경할 수 없습니다. 캐시된 문자열은 여러 고객 간에 공유되므로 한 고객의 작업이 다른 모든 고객에게 영향을 미칠 위험이 항상 존재합니다. 예를 들어, 코드 조각이 문자열 "Test"의 값을 "TEST"로 변경하면 다른 모든 클라이언트에도 해당 값이 표시됩니다. String 객체의 캐싱 성능은 중요한 측면이므로 String 클래스를 변경할 수 없도록 만들어 이러한 위험을 피하세요.
同时,String 是 final 的,因此没有人可以通过扩展和覆盖行为来破坏 String 类的不变性、缓存、散列值的计算等。String 类不可变的另一个原因可能是由于 HashMap。
由于把字符串作为 HashMap 键很受欢迎。对于键值来说,重要的是它们是不可变的,以便用它们检索存储在 HashMap 中的值对象。由于 HashMap 的工作原理是散列,因此需要具有相同的值才能正常运行。如果在插入后修改了 String 的内容,可变的 String将在插入和检索时生成两个不同的哈希码,可能会丢失 Map 中的值对象。
如果你是印度板球迷,你可能能够与我的下一句话联系起来。字符串是Java的 VVS Laxman,即非常特殊的类。我还没有看到一个没有使用 String 编写的 Java 程序。这就是为什么对 String 的充分理解对于 Java 开发人员来说非常重要。
String 作为数据类型,传输对象和中间人角色的重要性和流行性也使这个问题在 Java 面试中很常见。
为什么 String 在 Java 中是不可变的是 Java 中最常被问到的字符串访问问题之一,它首先讨论了什么是 String,Java 中的 String 如何与 C 和 C++ 中的 String 不同,然后转向在Java中什么是不可变对象,不可变对象有什么好处,为什么要使用它们以及应该使用哪些场景。
这个问题有时也会问:“为什么 String 在 Java 中是 final 的”。在类似的说明中,如果你正在准备Java 面试,我建议你看看《Java程序员面试宝典(第4版) 》,这是高级和中级Java程序员的优秀资源。它包含来自所有重要 Java 主题的问题,包括多线程,集合,GC,JVM内部以及 Spring和 Hibernate 框架等。
正如我所说,这个问题可能有很多可能的答案,而 String 类的唯一设计者可以放心地回答它。我在 Joshua Bloch 的 Effective Java 书中期待一些线索,但他也没有提到它。我认为以下几点解释了为什么 String 类在 Java 中是不可变的或 final 的:
1)想象字符串池没有使字符串不可变,它根本不可能,因为在字符串池的情况下,一个字符串对象/文字,例如 “Test” 已被许多参考变量引用,因此如果其中任何一个更改了值,其他参数将自动受到影响,即假设
String A="Test"; String B="Test";
现在字符串 B 调用 "Test".toUpperCase(), 将同一个对象改为“TEST”,所以 A 也是 “TEST”,这不是期望的结果。
下图显示了如何在堆内存和字符串池中创建字符串。
2)字符串已被广泛用作许多 Java 类的参数,例如,为了打开网络连接,你可以将主机名和端口号作为字符串传递,你可以将数据库 URL 作为字符串传递, 以打开数据库连接,你可以通过将文件名作为参数传递给 File I/O 类来打开 Java 中的任何文件。如果 String 不是不可变的,这将导致严重的安全威胁,我的意思是有人可以访问他有权授权的任何文件,然后可以故意或意外地更改文件名并获得对该文件的访问权限。由于不变性,你无需担心这种威胁。这个原因也说明了,为什么 String 在 Java 中是最终的,通过使 java.lang.String final,Java设计者确保没有人覆盖 String 类的任何行为。
3)由于 String 是不可变的,它可以安全地共享许多线程,这对于多线程编程非常重要. 并且避免了 Java 中的同步问题,不变性也使得String 实例在 Java 中是线程安全的,这意味着你不需要从外部同步 String 操作。关于 String 的另一个要点是由截取字符串 SubString 引起的内存泄漏,这不是与线程相关的问题,但也是需要注意的。
4)为什么 String 在 Java 中是不可变的另一个原因是允许 String 缓存其哈希码,Java 中的不可变 String 缓存其哈希码,并且不会在每次调用 String 的 hashcode 方法时重新计算,这使得它在 Java 中的 HashMap 中使用的 HashMap 键非常快。简而言之,因为 String 是不可变的,所以没有人可以在创建后更改其内容,这保证了 String 的 hashCode 在多次调用时是相同的。
5)String 不可变的绝对最重要的原因是它被类加载机制使用,因此具有深刻和基本的安全考虑。如果 String 是可变的,加载“java.io.Writer” 的请求可能已被更改为加载 “mil.vogoon.DiskErasingWriter”. 安全性和字符串池是使字符串不可变的主要原因。顺便说一句,上面的理由很好回答另一个Java面试问题: “为什么String在Java中是最终的”。要想是不可变的,你必须是最终的,这样你的子类不会破坏不变性。你怎么看?
另一个基于 String 的棘手 Java 问题,相信我只有很少的 Java 程序员可以正确回答这个问题。这是一个真正艰难的核心Java面试问题,并且需要对 String 的扎实知识才能回答这个问题。
这是最近在 Java 面试中向我的一位朋友询问的问题。他正在接受技术主管职位的面试,并且有超过6年的经验。如果你还没有遇到过这种情况,那么字符数组和字符串可以用来存储文本数据,但是选择一个而不是另一个很难。但正如我的朋友所说,任何与 String 相关的问题都必须对字符串的特殊属性有一些线索,比如不变性,他用它来说服访提问的人。在这里,我们将探讨为什么你应该使用char[]存储密码而不是String的一些原因。
字符串:
1)由于字符串在 Java 中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它. 并且为了可重用性,会存在 String 在字符串池中, 它很可能会保留在内存中持续很长时间,从而构成安全威胁。
由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[],你就可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。
2)Java 本身建议使用 JPasswordField 的 getPassword() 方法,该方法返回一个 char[] 和不推荐使用的getTex() 方法,该方法以明文形式返回密码,由于安全原因。应遵循 Java 团队的建议, 坚持标准而不是反对它。
3)使用 String 时,总是存在在日志文件或控制台中打印纯文本的风险,但如果使用 Array,则不会打印数组的内容而是打印其内存位置。虽然不是一个真正的原因,但仍然有道理。
String strPassword =“Unknown”; char [] charPassword = new char [] {'U','n','k','w','o','n'}; System.out.println(“字符密码:”+ strPassword); System.out.println(“字符密码:”+ charPassword);
输出
字符串密码:Unknown 字符密码:[C @110b053
我还建议使用散列或加密的密码而不是纯文本,并在验证完成后立即从内存中清除它。因此,在Java中,用字符数组用存储密码比字符串是更好的选择。虽然仅使用char[]还不够,还你需要擦除内容才能更安全。(实用详尽的Java面试题大全,可以在Java知音公众号回复“面试题聚合”)
这个 Java 问题也常被问: 什么是线程安全的单例,你怎么创建它。好吧,在Java 5之前的版本, 使用双重检查锁定创建单例 Singleton 时,如果多个线程试图同时创建 Singleton 实例,则可能有多个 Singleton 实例被创建。从 Java 5 开始,使用 Enum 创建线程安全的Singleton很容易。但如果面试官坚持双重检查锁定,那么你必须为他们编写代码。记得使用volatile变量。
为什么枚举单例在 Java 中更好
枚举单例是使用一个实例在 Java 中实现单例模式的新方法。虽然Java中的单例模式存在很长时间,但枚举单例是相对较新的概念,在引入Enum作为关键字和功能之后,从Java5开始在实践中。本文与之前关于 Singleton 的内容有些相关, 其中讨论了有关 Singleton 模式的面试中的常见问题, 以及 10 个 Java 枚举示例, 其中我们看到了如何通用枚举可以。这篇文章是关于为什么我们应该使用Eeame作为Java中的单例,它比传统的单例方法相比有什么好处等等。
Java 枚举和单例模式
Java 中的枚举单例模式是使用枚举在 Java 中实现单例模式。单例模式在 Java 中早有应用, 但使用枚举类型创建单例模式时间却不长. 如果感兴趣, 你可以了解下构建者设计模式和装饰器设计模式。
1) 枚举单例易于书写
这是迄今为止最大的优势,如果你在Java 5之前一直在编写单例, 你知道, 即使双检查锁定, 你仍可以有多个实例。虽然这个问题通过 Java 内存模型的改进已经解决了, 从 Java 5 开始的 volatile 类型变量提供了保证, 但是对于许多初学者来说, 编写起来仍然很棘手。与同步双检查锁定相比,枚举单例实在是太简单了。如果你不相信, 那就比较一下下面的传统双检查锁定单例和枚举单例的代码:
在 Java 中使用枚举的单例
这是我们通常声明枚举的单例的方式,它可能包含实例变量和实例方法,但为了简单起见,我没有使用任何实例方法,只是要注意,如果你使用的实例方法且该方法能改变对象的状态的话, 则需要确保该方法的线程安全。默认情况下,创建枚举实例是线程安全的,但 Enum 上的任何其他方法是否线程安全都是程序员的责任。
/** * 使用 Java 枚举的单例模式示例 */ public enum EasySingleton{ INSTANCE; }
你可以通过EasySingleton.INSTANCE来处理它,这比在单例上调用getInstance()方法容易得多。
具有双检查锁定的单例示例
下面的代码是单例模式中双重检查锁定的示例,此处的 getInstance() 方法检查两次,以查看 INSTANCE 是否为空,这就是为什么它被称为双检查锁定模式,请记住,双检查锁定是代理之前Java 5,但Java5内存模型中易失变量的干扰,它应该工作完美。
/** * 单例模式示例,双重锁定检查 */ public class DoubleCheckedLockingSingleton{ private volatile DoubleCheckedLockingSingleton INSTANCE; private DoubleCheckedLockingSingleton(){} public DoubleCheckedLockingSingleton getInstance(){ if(INSTANCE == null){ synchronized(DoubleCheckedLockingSingleton.class){ //double checking Singleton instance if(INSTANCE == null){ INSTANCE = new DoubleCheckedLockingSingleton(); } } } return INSTANCE; } }
你可以调用DoubleCheckedLockingSingleton.getInstance() 来获取此单例类的访问权限。
现在,只需查看创建延迟加载的线程安全的 Singleton 所需的代码量。使用枚举单例模式, 你可以在一行中具有该模式, 因为创建枚举实例是线程安全的, 并且由 JVM 进行。
人们可能会争辩说,有更好的方法来编写 Singleton 而不是双检查锁定方法, 但每种方法都有自己的优点和缺点, 就像我最喜欢在类加载时创建的静态字段 Singleton, 如下面所示, 但请记住, 这不是一个延迟加载单例:
单例模式用静态工厂方法
这是我最喜欢的在 Java 中影响 Singleton 模式的方法之一,因为 Singleton 实例是静态的,并且最后一个变量在类首次加载到内存时初始化,因此实例的创建本质上是线程安全的。
/** * 单例模式示例与静态工厂方法 */ public class Singleton{ //initailzed during class loading private static final Singleton INSTANCE = new Singleton(); //to prevent creating another instance of Singleton private Singleton(){} public static Singleton getSingleton(){ return INSTANCE; } }
你可以调用 Singleton.getSingleton() 来获取此类的访问权限。
2) 枚举单例自行处理序列化
传统单例的另一个问题是,一旦实现可序列化接口,它们就不再是 Singleton, 因为 readObject() 方法总是返回一个新实例, 就像 Java 中的构造函数一样。通过使用 readResolve() 方法, 通过在以下示例中替换 Singeton 来避免这种情况:
//readResolve to prevent another instance of Singleton private Object readResolve(){ return INSTANCE; }
如果 Singleton 类保持内部状态, 这将变得更加复杂, 因为你需要标记为 transient(不被序列化),但使用枚举单例, 序列化由 JVM 进行。
3) 创建枚举实例是线程安全的
如第 1 点所述,因为 Enum 实例的创建在默认情况下是线程安全的, 你无需担心是否要做双重检查锁定。
总之, 在保证序列化和线程安全的情况下,使用两行代码枚举单例模式是在 Java 5 以后的世界中创建 Singleton 的最佳方式。你仍然可以使用其他流行的方法, 如你觉得更好, 欢迎讨论。
经典但核心Java面试问题之一。
如果你没有参与过多线程并发 Java 应用程序的编码,你可能会失败。
(视频教程推荐:java课程)
如何避免 Java 线程死锁?
如何避免 Java 中的死锁?是 Java 面试的热门问题之一, 也是多线程的编程中的重口味之一, 主要在招高级程序员时容易被问到, 且有很多后续问题。尽管问题看起来非常基本, 但大多数 Java 开发人员一旦你开始深入, 就会陷入困境。
面试问题总是以“什么是死锁?”开始
当两个或多个线程在等待彼此释放所需的资源(锁定)并陷入无限等待即是死锁。它仅在多任务或多线程的情况下发生。
如何检测 Java 中的死锁?
虽然这可以有很多答案, 但我的版本是首先我会看看代码, 如果我看到一个嵌套的同步块,或从一个同步的方法调用其他同步方法, 或试图在不同的对象上获取锁, 如果开发人员不是非常小心,就很容易造成死锁。
另一种方法是在运行应用程序时实际锁定时找到它, 尝试采取线程转储,在 Linux 中,你可以通过kill -3命令执行此操作, 这将打印应用程序日志文件中所有线程的状态, 并且你可以看到哪个线程被锁定在哪个线程对象上。
你可以使用 fastthread.io 网站等工具分析该线程转储, 这些工具允许你上载线程转储并对其进行分析。
另一种方法是使用 jConsole 或 VisualVM, 它将显示哪些线程被锁定以及哪些对象被锁定。
如果你有兴趣了解故障排除工具和分析线程转储的过程, 我建议你看看 Uriah Levy 在多元视觉(PluraIsight)上《分析 Java 线程转储》课程。旨在详细了解 Java 线程转储, 并熟悉其他流行的高级故障排除工具。
()
编写一个将导致死锁的Java程序?
一旦你回答了前面的问题,他们可能会要求你编写代码,这将导致Java死锁。
这是我的版本之一
/** * Java 程序通过强制循环等待来创建死锁。 * * */ public class DeadLockDemo { /* * 此方法请求两个锁,第一个字符串,然后整数 */ public void method1() { synchronized (String.class) { System.out.println("Aquired lock on String.class object"); synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); } } } /* * 此方法也请求相同的两个锁,但完全 * 相反的顺序,即首先整数,然后字符串。 * 如果一个线程持有字符串锁,则这会产生潜在的死锁 * 和其他持有整数锁,他们等待对方,永远。 */ public void method2() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } } }
如果 method1() 和 method2() 都由两个或多个线程调用,则存在死锁的可能性, 因为如果线程 1 在执行 method1() 时在 Sting 对象上获取锁, 线程 2 在执行 method2() 时在 Integer 对象上获取锁, 等待彼此释放 Integer 和 String 上的锁以继续进行一步, 但这永远不会发生。
此图精确演示了我们的程序, 其中一个线程在一个对象上持有锁, 并等待其他线程持有的其他对象锁。
你可以看到, Thread1 需要 Thread2 持有的 Object2 上的锁,而 Thread2 希望获得 Thread1 持有的 Object1 上的锁。由于没有线程愿意放弃, 因此存在死锁, Java 程序被卡住。
其理念是, 你应该知道使用常见并发模式的正确方法, 如果你不熟悉这些模式,那么 Jose Paumard 《应用于并发和多线程的常见 Java 模式》是学习的好起点。
如何避免Java中的死锁?
现在面试官来到最后一部分, 在我看来, 最重要的部分之一; 如何修复代码中的死锁?或如何避免Java中的死锁?
如果你仔细查看了上面的代码,那么你可能已经发现死锁的真正原因不是多个线程, 而是它们请求锁的方式, 如果你提供有序访问, 则问题将得到解决。
下面是我的修复版本,它通过避免循环等待,而避免死锁, 而不需要抢占, 这是需要死锁的四个条件之一。
public class DeadLockFixed { /** * 两种方法现在都以相同的顺序请求锁,首先采用整数,然后是 String。 * 你也可以做反向,例如,第一个字符串,然后整数, * 只要两种方法都请求锁定,两者都能解决问题 * 顺序一致。 */ public void method1() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } } public void method2() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } } }
现在没有任何死锁,因为两种方法都按相同的顺序访问 Integer 和 String 类文本上的锁。因此,如果线程 A 在 Integer 对象上获取锁, 则线程 B 不会继续, 直到线程 A 释放 Integer 锁, 即使线程 B 持有 String 锁, 线程 A 也不会被阻止, 因为现在线程 B 不会期望线程 A 释放 Integer 锁以继续。(实用详尽的Java面试题大全,可以在Java知音公众号回复“面试题聚合”)
任何序列化该类的尝试都会因NotSerializableException而失败,但这可以通过在 Java中 为 static 设置瞬态(trancient)变量来轻松解决。
Java 序列化相关的常见问题
Java 序列化是一个重要概念, 但它很少用作持久性解决方案, 开发人员大多忽略了 Java 序列化 API。根据我的经验, Java 序列化在任何 Java核心内容面试中都是一个相当重要的话题, 在几乎所有的网面试中, 我都遇到过一两个 Java 序列化问题, 我看过一次面试, 在问几个关于序列化的问题之后候选人开始感到不自在, 因为缺乏这方面的经验。
他们不知道如何在 Java 中序列化对象, 或者他们不熟悉任何 Java 示例来解释序列化, 忘记了诸如序列化在 Java 中如何工作, 什么是标记接口, 标记接口的目的是什么, 瞬态变量和可变变量之间的差异, 可序列化接口具有多少种方法, 在 Java 中,Serializable 和 Externalizable 有什么区别, 或者在引入注解之后, 为什么不用 @Serializable 注解或替换 Serializalbe 接口。
在本文中,我们将从初学者和高级别进行提问, 这对新手和具有多年 Java 开发经验的高级开发人员同样有益。
关于Java序列化的10个面试问题
大多数商业项目使用数据库或内存映射文件或只是普通文件, 来满足持久性要求, 只有很少的项目依赖于 Java 中的序列化过程。无论如何,这篇文章不是 Java 序列化教程或如何序列化在 Java 的对象, 但有关序列化机制和序列化 API 的面试问题, 这是值得去任何 Java 面试前先看看以免让一些未知的内容惊到自己。
Java 직렬화에 익숙하지 않은 분들을 위해 설명하자면, Java 직렬화란 객체의 상태를 확장자가 .ser인 파일에 저장하여 Java에서 객체를 직렬화하는 데 사용되는 프로세스이며, 이 파일을 통해 Java를 복원하고 재구성할 수 있습니다. 개체 상태에서 이 역 프로세스를 역직렬화라고 합니다.
Java 직렬화란
직렬화는 객체를 디스크에 저장하거나 네트워크를 통해 실행 중인 다른 Java 가상 머신으로 보낼 수 있는 바이너리 형식으로 변경하는 프로세스이며, Java 직렬화 해제를 통해 객체 상태를 복원할 수 있습니다. Java API는 개발자에게 java.io.Serialized 및 java.io.Externalized 인터페이스, ObjectInputStream 및 ObjectOutputStream을 통해 객체 직렬화를 처리하는 표준 메커니즘을 제공합니다. Java 프로그래머는 클래스 구조 또는 자체 정의된 바이너리 형식을 기반으로 표준 직렬화를 자유롭게 선택할 수 있습니다. , 직렬화된 바이너리 파일 형식이 클래스 출력 API의 일부가 되어 Java에서 개인 및 패키지 표시 속성의 캡슐화를 깨뜨릴 수 있기 때문에 후자가 일반적으로 모범 사례로 간주됩니다.
직렬화 방법
Let 클래스. Java에서는 매우 쉽게 직렬화할 수 있습니다. Java 클래스는 java.io.Serialized 인터페이스만 구현하면 되며 JVM은 기본 형식으로 객체 객체를 직렬화합니다. 클래스 직렬화에는 오랜 시간이 걸릴 수 있습니다. -term 비용으로 인해 구현을 수정하거나 변경하는 것이 제한될 수 있습니다. 클래스를 구현하여 인터페이스를 추가하여 클래스의 구조를 변경하면 필드를 추가하거나 제거하면 사용자 정의 바이너리 형식으로 수행할 수 있는 기본 직렬화가 중단될 수 있습니다. 비호환 가능성을 최소화하지만 이전 버전과의 호환성을 보장하려면 여전히 상당한 노력이 필요합니다. 직렬화가 클래스 변경 기능을 제한하는 방법에 대한 한 가지 예는 SerialVersionUID입니다.
SerialVersionUID를 명시적으로 선언하지 않으면 JVM은 클래스 구현 인터페이스와 변경될 수 있는 기타 여러 요소에 따라 달라지는 클래스 구조를 기반으로 구조를 생성합니다. 새 버전의 클래스 파일이 다른 인터페이스를 구현한다고 가정하면 JVM은 다른 SerialVersionUID를 생성하고 이전 버전의 프로그램에서 직렬화된 이전 개체를 로드하려고 하면 InvalidClassException이 발생합니다.
질문 1) Java에서 직렬화 가능 인터페이스와 외부 인터페이스의 차이점은 무엇입니까?
Java 직렬화 인터뷰에서 가장 자주 묻는 질문입니다. 아래는 내 버전입니다. 외부화 가능은 writeExternal() 및 readExternal() 메소드를 제공합니다. 이를 통해 Java의 기본 직렬화에 의존하는 대신 Java 직렬화 메커니즘을 유연하게 제어할 수 있습니다. 외부화 가능 인터페이스를 올바르게 구현하면 애플리케이션 성능이 크게 향상될 수 있습니다.
질문 2) 직렬화 가능한 메소드는 몇 개인가요? 메서드가 없다면 직렬화 가능 인터페이스를 어떻게 사용하나요?
Serialized Serializalbe 인터페이스는 java.io 패키지에 존재하며 Java 직렬화 메커니즘의 핵심을 형성합니다. Java에서는 태그가 지정된 인터페이스라고도 알려진 메소드가 없습니다. 클래스가 java.io.Serialized 인터페이스를 구현하면 Java에서 직렬화 가능해지고 컴파일러에게 Java 직렬화 메커니즘을 사용하여 이 객체를 직렬화하도록 지시합니다.
질문 3) serialVersionUID가 무엇인가요? 이것을 정의하지 않으면 어떻게 될까요?
Java 직렬화에 관해 제가 가장 좋아하는 인터뷰 질문 중 하나입니다. serialVersionUID는 개체에 인쇄되는 개인 정적 최종 긴 ID이며 일반적으로 개체의 해시 코드입니다. JDK 도구 serialver를 사용하여 직렬화된 개체의 serialVersionUID를 볼 수 있습니다. SerialVerionUID는 객체 버전 관리에 사용됩니다. serialVersionUID는 클래스 파일에서도 지정할 수 있습니다. serialVersionUID를 지정하지 않으면 클래스의 필드를 추가하거나 수정할 때 새 클래스와 이전 직렬화된 개체에 대해 생성된 serialVersionUID가 다르기 때문에 직렬화된 클래스가 복원되지 않습니다. Java 직렬화 프로세스는 상태를 복원하기 위해 객체의 올바른 직렬화에 의존하며 직렬화된 객체의 직렬 버전이 일치하지 않는 경우 java.io.InvalidClassException을 발생시킵니다. serialVersionUID에 대한 자세한 내용은 FQ 문서를 참조하세요. 필수의.
질문4) 연재시 일부 멤버가 연재되지 않도록 하시겠습니까? 어떻게 달성하나요?
연재 면접에 자주 묻는 또 다른 질문입니다. 임시 변수가 무엇인지, 임시 변수와 정적 변수가 직렬화되는지 등과 같은 질문도 있습니다. 따라서 어떤 필드도 객체 상태의 일부로 포함하지 않으려면 정적 또는 임시 변수로 선언하세요. 귀하의 필요에 따라 이는 Java 직렬화 프로세스에 포함되지 않습니다.
질문 5) 클래스 멤버가 직렬화 가능 인터페이스를 구현하지 않으면 어떻게 되나요?
Java 직렬화 프로세스에 대한 간단한 질문입니다. 직렬화 가능을 구현하는 클래스의 객체를 직렬화하려고 하지만 객체에 직렬화 가능하지 않은 클래스에 대한 참조가 포함되어 있으면 런타임에 NotSerializedException이 발생합니다. 이것이 바로 제가 항상 직렬화 가능 경고(내 코드에)를 넣는 이유입니다. 주석 섹션), 코드 주석 모범 사례 중 하나는 개발자에게 직렬화 가능 클래스에 새 필드를 추가할 때 이 사실을 염두에 두도록 지시합니다.
질문 6) 클래스는 직렬화 가능하지만 슈퍼클래스는 그렇지 않은 경우, 직렬화 해제 후 슈퍼클래스에서 상속된 인스턴스 변수의 상태는 어떻습니까?
Java 직렬화 프로세스는 객체 계층이 직렬화 가능한 구조일 때만 계속됩니다. 즉, Java에서 직렬화 가능한 인터페이스가 구현되고 슈퍼 클래스에서 상속된 인스턴스 변수의 값이 생성자를 호출하여 초기화됩니다. 직렬화 중에 직렬화할 수 없는 슈퍼클래스입니다. 생성자 연결이 시작되면 중지할 수 없으므로 계층 구조의 상위 클래스가 직렬화 가능 인터페이스를 구현하더라도 생성자는 실행됩니다. 성명서에서 볼 수 있듯이 이번 연재 면접 질문은 매우 까다롭고 어려워 보이지만 핵심 개념을 숙지하고 있다면 어렵지 않습니다.
질문 7) 직렬화 프로세스를 사용자 정의할 수 있습니까, 아니면 Java에서 기본 직렬화 프로세스를 재정의할 수 있습니까?
답은 '예, 가능합니다'입니다. 우리 모두는 객체를 직렬화하려면 ObjectOutputStream.writeObject(saveThisObject)를 호출하고 ObjectInputStream.readObject()를 사용하여 객체를 읽어야 한다는 것을 알고 있습니다. 그러나 Java 가상 머신이 제공하는 또 하나의 기능은 이 두 가지 메소드를 정의하는 것입니다. 이 두 메서드가 클래스에 정의된 경우 JVM은 기본 직렬화 메커니즘을 적용하는 대신 이 두 메서드를 호출합니다. 여기서는 모든 종류의 전처리 또는 후처리 작업을 수행하여 객체 직렬화 및 역직렬화 동작을 사용자 정의할 수 있습니다.
주의해야 할 중요한 점은 상속, 재정의 또는 오버로드를 방지하기 위해 이러한 메서드를 비공개로 선언하는 것입니다. Java 가상 머신만이 클래스의 전용 메소드를 호출할 수 있으므로 클래스의 무결성이 유지되고 Java 직렬화가 제대로 작동합니다. 제 생각에는 이것은 Java 직렬화 인터뷰에서 물어볼 수 있는 가장 좋은 질문 중 하나이며, 좋은 후속 질문은 왜 객체에 사용자 정의 직렬화 형식을 제공합니까?입니다.
질문 8) 새 클래스의 슈퍼 클래스가 직렬화 가능한 인터페이스를 구현한다고 가정할 때, 새 클래스가 직렬화되는 것을 방지하는 방법은 무엇입니까?
Java 직렬화에 대한 어려운 인터뷰 질문입니다. 클래스의 Super 클래스가 Java에서 직렬화 가능 인터페이스를 구현한 경우 이미 Java에서 직렬화 가능합니다. 인터페이스를 취소할 수 없기 때문에 실제로 클래스를 직렬화 불가능하게 만들 수는 없지만 새로운 오류를 피할 수 있는 방법은 있습니다. 클래스 직렬화. Java 직렬화를 방지하려면 클래스에 writeObject() 및 readObject() 메서드를 구현해야 하며 이 메서드에서 NotSerializedException을 발생시켜야 합니다. 이는 위의 직렬화 인터뷰 질문에서 언급한 것처럼 Java 직렬화 프로세스를 사용자 정의하면 얻을 수 있는 또 다른 이점이며, 인터뷰가 진행됨에 따라 후속 질문으로 자주 묻는 경우도 있습니다.
질문 9) Java에서는 직렬화 및 역직렬화 과정에서 어떤 방법을 사용하나요?
이것은 기본적으로 직렬화에서 면접관이 알고자 하는 매우 일반적인 면접 질문입니다. readObject(), writeObject(), readExternal() 및 writeExternal()의 사용법에 익숙하십니까? Java 직렬화는 java.io.ObjectOutputStream 클래스에 의해 수행됩니다. 이 클래스는 직렬화 메커니즘을 처리하기 위해 하위 수준 바이트 스트림에 캡슐화된 필터 스트림입니다. 직렬화 메커니즘을 통해 객체를 저장하려면 ObjectOutputStream.writeObject(savethisobject)를 호출하고, 객체를 역직렬화하려면 ObjectInputStream.readObject() 메서드를 호출합니다. writeObject() 메서드를 호출하면 java에서 직렬화 프로세스가 트리거됩니다. readObject() 메서드에 대해 주목해야 할 중요한 점은 지속성에서 바이트를 읽고, 해당 바이트에서 객체를 생성하고, 올바른 유형으로 유형 캐스팅이 필요한 객체를 반환하는 데 사용된다는 것입니다.
질문 10) 직렬화되어 지속성으로 저장된 클래스가 있고 클래스를 수정하여 새 필드를 추가한다고 가정해 보겠습니다. 직렬화된 객체를 역직렬화하면 어떻게 되나요?
클래스에 자체 serialVersionUID가 있는지 여부에 따라 다릅니다. 위의 질문에서 알 수 있듯이 serialVersionUID를 제공하지 않으면 Java 컴파일러가 이를 생성하며 일반적으로 객체의 해시 코드와 동일합니다. 새 필드를 추가하면 새 버전의 클래스에 대해 생성된 새 serialVersionUID가 이미 직렬화된 객체와 다를 가능성이 있습니다. 이 경우 Java 직렬화 API는 java.io.InvalidClassException을 발생시킵니다. 코드 serialVersionUID에 고유한 코드를 포함하고 단일 클래스 내에서 일정하게 유지되도록 하는 것이 좋습니다.
11) Java 직렬화 메커니즘의 호환 가능한 변경 사항과 호환되지 않는 변경 사항은 무엇입니까?
실제 과제는 필드, 메소드를 추가하거나 직렬화된 객체를 사용하여 필드 또는 메소드를 제거하여 클래스 구조를 변경하는 것입니다. Java 직렬화 사양에 따르면 필드나 메서드를 추가하면 클래스 계층 구조에 대한 호환되지 않는 변경 및 변경이 발생하거나 직렬화 가능 인터페이스의 구현이 해제될 수 있으며, 그 중 일부는 호환되지 않는 변경이 발생할 수 있습니다. 호환 가능한 변경 사항과 호환되지 않는 변경 사항의 전체 목록을 보려면 Java 직렬화 사양을 읽어 보시기 바랍니다.
12) 직렬화된 객체를 네트워크를 통해 전송할 수 있나요?
예, 직렬화된 객체는 네트워크를 통해 전송할 수 있습니다. 왜냐하면 Java 직렬화된 객체는 여전히 바이트 형태이고 바이트는 네트워크를 통해 전송될 수 있기 때문입니다. 직렬화된 개체를 디스크나 데이터베이스에 Blob으로 저장할 수도 있습니다.
13) Java 직렬화 중에 직렬화되지 않는 변수는 무엇입니까?
이 질문은 다르게 묻는 것이지만 목적은 여전히 동일합니다. Java 개발자는 정적 변수와 임시 변수의 세부 사항을 알고 있습니까? 정적 변수는 객체가 아닌 클래스에 속하므로 객체 상태의 일부가 아니므로 Java 직렬화 중에 저장되지 않습니다. Java 직렬화는 객체 자체가 아닌 객체의 상태만 유지하므로 임시 변수도 Java 직렬화 프로세스에 포함되지 않으며 객체의 직렬화 상태의 일부가 아닙니다. 이 질문을 한 후 면접관은 후속 질문을 할 것입니다. 이러한 변수의 값을 저장하지 않은 경우 이러한 개체를 역직렬화하고 다시 생성하면 이러한 변수의 값은 무엇입니까? 이것이 당신이 고려해야 할 사항입니다.
또 다른 까다로운 핵심 Java 문제입니다. 기다려서 알려주세요. wait 및 수정은 wait 또는 inform-get이 호출되는 개체를 모니터링해야 하기 때문에 동기화 표시 메서드 또는 동기화 블록에서 호출됩니다.
대부분의 Java 개발자는 객체 클래스의 wait(), inform() 및 informAll() 메소드가 Java의 동기화 메소드 또는 동기화 블록에서 호출되어야 한다는 것을 알고 있지만, 우리는 왜 Java에서 대기하고, 통지하고, informAll은 동기화된 블록이나 메소드에서 왔습니까?
최근 Java 인터뷰에서 내 친구 중 한 명이 이 질문을 받았는데, 그는 그것에 대해 생각하고 다음과 같이 대답했습니다. wait() 또는 inform(동기화된 컨텍스트에서) 메소드를 호출하지 않으면 Java에서 IllegalMonitorStateException을 수신합니다.
그의 대답은 사실상 정확하지만 면접관은 그러한 대답에 완전히 만족하지 않고 그에게 문제를 설명하고 싶어합니다. 인터뷰 후 그와 나는 같은 문제에 대해 논의했고 나는 그가 면접관에게 Java의 wait()와 inform() 사이의 경쟁 조건에 대해 말해야 한다고 생각했습니다. 이는 동기화된 메서드나 블록에서 호출하지 않으면 존재할 수 있습니다.
Java 프로그램에서 경쟁 조건이 어떻게 발생하는지 살펴보겠습니다. 이는 또한 인기 있는 스레드 인터뷰 질문 중 하나이며 전화 및 대면 Java 개발자 인터뷰에서 자주 등장합니다. 그래서 자바 면접을 준비한다면 이런 질문을 준비해야 하는데, 정말 도움이 될 수 있는 책 중 하나가 자바 프로그래머 면접 공식집이다. 이 책은 핵심 Java, 멀티스레딩, IO 및 NIO, Spring 및 Hibernate와 같은 프레임워크 등 Java 인터뷰의 거의 모든 중요한 주제를 다루는 보기 드문 책입니다. 여기에서 확인하실 수 있습니다.
왜 Java의 동기화된 메소드에서 wait 메소드를 호출해야 하는가? 왜 Java의 동기화된 블록이나 메소드에서 wait 메소드를 호출해야 합니까? 우리는 Java에서 스레드 간 통신을 위해 주로 wait(), inform() 또는 informAll() 메소드를 사용합니다. 스레드는 조건을 확인한 후 대기합니다. 예를 들어 고전적인 생산자-소비자 문제에서 버퍼가 가득 차면 생산자 스레드는 기다리고 소비자 스레드는 요소나 스레드를 사용하여 버퍼에 공간을 만든 후 생산자에게 알립니다. 조건이 변경되었음을 단일 또는 다중 스레드에 알리려면 통지() 또는 informAll() 메소드를 호출하고, 통지 스레드가 동기화된 블록을 떠나면 모든 대기 스레드는 대기 객체 잠금을 획득하기 시작하고 행운의 스레드는 잠금 후 다시 획득합니다. , wait() 메서드에서 반환하고 계속합니다.
Java에서 wait() 및 inform() 메소드 사이의 경쟁 조건 가능성을 확인하기 위해 전체 작업을 단계로 나누어 보겠습니다. 시나리오를 더 잘 이해하기 위해 Produce Consumer 스레드 예를 사용하겠습니다.
Producer 스레드 테스트 조건(버퍼가 가득 찼는지 여부)을 확인하고 대기해야 함을 확인합니다(버퍼가 가득 찼음을 확인).
소비자 스레드는 버퍼의 요소를 소비한 후 조건을 설정합니다.
소비자 스레드는 inform() 메서드를 호출합니다. 생산자 스레드가 아직 기다리고 있지 않기 때문에 이 메시지가 들리지 않습니다.
생산자 스레드는 wait() 메서드를 호출하고 대기 상태로 들어갑니다.
따라서 경쟁 조건으로 인해 알림이 손실될 수 있습니다. 버퍼를 사용하거나 요소 하나만 사용하면 프로덕션 스레드가 영원히 기다리며 프로그램이 중단됩니다. "Java 동기화에서 알림 및 통지를 기다리는 중 이제 이 잠재적 경쟁 조건을 해결하는 방법을 고려해 보겠습니다.
이 경쟁 조건은 Java에서 제공하는 동기화 키워드 및 잠금을 사용하여 해결됩니다. wait(), inform()을 호출하려면 또는 informAll()을 사용하려면 Java에서 메소드를 호출하는 객체에 대한 잠금을 획득해야 합니다. Java의 wait() 메서드는 대기하기 전에 잠금을 해제하고 wait()에서 반환되기 전에 잠금을 다시 획득해야 합니다. 이 잠금을 사용하면 조건 확인(버퍼가 가득 찼는지 여부)과 조건 설정(버퍼에서 요소 가져오기)이 원자적으로 이루어지도록 보장하며, 이는 Java
의 동기화된 메서드나 블록을 사용하여 달성할 수 있습니다.我不确定这是否是面试官实际期待的,但这个我认为至少有意义,请纠正我如果我错了,请告诉我们是否还有其他令人信服的理由调用 wait(),notify() 或 Java 中的 notifyAll() 方法。
总结一下,我们用 Java 中的 synchronized 方法或 synchronized 块调用 Java 中的 wait(),notify() 或 notifyAll() 方法来避免:
1) Java 会抛出 IllegalMonitorStateException,如果我们不调用来自同步上下文的wait(),notify()或者notifyAll()方法。
2) Javac 中 wait 和 notify 方法之间的任何潜在竞争条件。
不,你不能在Java中覆盖静态方法,但在子类中声明一个完全相同的方法不是编译时错误,这称为隐藏在Java中的方法。
你不能覆盖Java中的静态方法,因为方法覆盖基于运行时的动态绑定,静态方法在编译时使用静态绑定进行绑定。虽然可以在子类中声明一个具有相同名称和方法签名的方法,看起来可以在Java中覆盖静态方法,但实际上这是方法隐藏。Java不会在运行时解析方法调用,并且根据用于调用静态方法的 Object 类型,将调用相应的方法。这意味着如果你使用父类的类型来调用静态方法,那么原始静态将从父类中调用,另一方面如果你使用子类的类型来调用静态方法,则会调用来自子类的方法。简而言之,你无法在Java中覆盖静态方法。如果你使用像Eclipse或Netbeans这样的Java IDE,它们将显示警告静态方法应该使用类名而不是使用对象来调用,因为静态方法不能在Java中重写。
/** * * Java program which demonstrate that we can not override static method in Java. * Had Static method can be overridden, with Super class type and sub class object * static method from sub class would be called in our example, which is not the case. */ public class CanWeOverrideStaticMethod { public static void main(String args[]) { Screen scrn = new ColorScreen(); //if we can override static , this should call method from Child class scrn.show(); //IDE will show warning, static method should be called from classname } } class Screen{ /* * public static method which can not be overridden in Java */ public static void show(){ System.out.printf("Static method from parent class"); } } class ColorScreen extends Screen{ /* * static method of same name and method signature as existed in super * class, this is not method overriding instead this is called * method hiding in Java */ public static void show(){ System.err.println("Overridden static method in Child Class in Java"); } }
输出:
Static method from parent class
此输出确认你无法覆盖Java中的静态方法,并且静态方法基于类型信息而不是基于Object进行绑定。如果要覆盖静态mehtod,则会调用子类或 ColorScreen 中的方法。这一切都在讨论中我们可以覆盖Java中的静态方法。我们已经确认没有,我们不能覆盖静态方法,我们只能在Java中隐藏静态方法。创建具有相同名称和mehtod签名的静态方法称为Java隐藏方法。IDE将显示警告:"静态方法应该使用类名而不是使用对象来调用", 因为静态方法不能在Java中重写。
这些是我的核心Java面试问题和答案的清单。对于有经验的程序员来说,一些Java问题看起来并不那么难,但对于Java中的中级和初学者来说,它们真的很难回答。顺便说一句,如果你在面试中遇到任何棘手的Java问题,请与我们分享。
相关推荐:java入门教程
위 내용은 Java 면접 질문 10가지를 알고 계시나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!