これは、Java の面接で最も難しい質問 10 個を集めたものです。これらの質問は主に Java の中核部分からのものであり、Java EE 関連の問題には関係ありません。これらの Java に関する難しい質問の答えを知っているかもしれませんし、Java の知識が十分に問われていないと感じているかもしれません。しかし、これらの質問は Java のさまざまな面接で簡単に尋ねられ、私の友人や同僚のプログラマーを含む多くの人が答えるのが難しいと感じています。
(その他のインタビュー質問の推奨事項: java インタビューの質問と回答)
Java の難しい質問です。Java プログラミング言語が自分で設計したものではない場合、この質問にどう答えることができますか。 Java プログラミングに関する一般的な知識と深い理解は、この Java 面接の難しい核心的な質問に答えるのに役立ちます。
wait、notify、notifyAll が Thread クラスではなく Object クラスで定義されている理由
これは Java の有名な面接の質問、採用 2 ~ 4 です。長年の経験を持つ上級 Java 開発者へのインタビュー。
この質問の良い点は、待機通知メカニズムに対する面接官の理解と、このトピックに対する面接官の理解が明確であるかどうかを反映していることです。 Java ではなぜ多重継承がサポートされないのか、または Java では String が Final であるのかという質問と同様、この質問には複数の答えがある可能性があります。
待機メソッドと通知メソッドが Object クラスで定義されている理由は、誰もがいくつかの理由を説明できます。私の面接の経験から判断すると、ほとんどの Java プログラマ、特に経験 2 ~ 3 年の開発者にとって、wait と nofity は依然として最も混乱するものであり、wait と notify を使用するように求められたら、彼らは混乱するでしょう。したがって、Java の面接を受ける場合は、待機と通知のメカニズムを十分に理解しており、待機を使用してコードを簡単に作成できること、プロデューサーとコンシューマーの問題やブロッキング キューの実装などによる通知のメカニズムを理解していることを確認してください。
wait と Notice を同期されたブロックまたはメソッドから呼び出す必要がある理由と、Java の wait、sleep、および yield メソッドの違いについては、まだ読んでいない場合は読むと興味深いでしょう。 wait、notify、notifyAll が Object クラスに属するのはなぜですか? なぜ Thread クラスに含めるべきではないのでしょうか? 以下に、私が理にかなっていると思うアイデアをいくつか示します:
1) wait と Notice は単なる普通のメソッドではありませんまたは同期ツール、そしてより重要なことに、それらは Java の 2 つのスレッド間の通信メカニズムです。 言語設計者にとって、この通信メカニズムが Java キーワード (synchronized など) を介して実装できないと同時に、このメカニズムがすべてのオブジェクトで利用可能であることを確認する場合、Object クラスが正しい宣言場所となります。同期と通知の待機は 2 つの異なる領域であることに注意してください。これらを同じまたは関連するものとして扱わないでください。同期は相互排他を提供し、Java クラスのスレッドの安全性を確保します。一方、待機と通知は 2 つのスレッド間の通信メカニズムです。
2) すべてのオブジェクトはロックできるため、Thread クラスではなく Object クラスで wait と notification を宣言するもう 1 つの理由があります。
3) Java のコードのクリティカル セクションに入るには、スレッドはロックしてロックを待つ必要があります。どのスレッドがロックを保持しているかはわかりません。ただし、ロックが特定のスレッドの保持によって保持されていることだけがわかっているため、どのスレッドが同期ブロック内にあるかを認識してロックの解放を要求するのではなく、ロックを取得するまで待機する必要があります。
4) Java は、Hoare のモニターのアイデアに基づいています。 Java では、すべてのオブジェクトにモニターがあります。
スレッドはモニター上で待機します。待機を実行するには、2 つのパラメータが必要です:
One thread
Oneモニター (任意のオブジェクト)
Java 設計では、スレッドを指定することはできません。常に、現在のコードを実行しているスレッドになります。ただし、モニター (これを待機オブジェクトと呼びます) を指定することはできます。これは良い設計です。なぜなら、他のスレッドが目的のモニターで待機している場合、同時プログラムの設計時に困難を引き起こす「侵入」につながるからです。 Java では、別のスレッドの実行に侵入するすべての操作 (stop メソッドなど) が非推奨であることに注意してください。
この Java コアの質問は答えるのが難しいと思います。なぜなら、あなたの答えは面接官を満足させない可能性があるからです。ほとんどの場合、面接官は答えの重要なポイントを探しているので、これらの重要なポイントについて言及すると、面接官は次のようなことを言うでしょう。幸せになる。 Java に関するこのような難しい質問に答えるための鍵は、その後に起こり得るさまざまな質問に対処するために、関連するトピックを準備しておくことです。
これは非常に古典的な質問で、Java で String が不変である理由とよく似ています。これら 2 つの質問の類似点は、主に Java の作成者による設計上の決定によるものであるということです。
Java が多重継承をサポートしない理由としては、次の 2 つの点が考えられます。
1) 最初の理由は、ひし形の継承の問題に関するあいまいさです。、foo() メソッドを持つクラス A を考えてみましょう。次に、B と C は A から派生し、独自のメソッドを持っています。 foo() の実装では、現在クラス D は B と C から派生した多重継承を使用しています。foo() のみを参照すると、コンパイラーはどの foo() を呼び出す必要があるかを決定できません。この継承スキームの構造がダイヤモンドに似ているため、これはダイヤモンド問題とも呼ばれます。以下を参照してください:
A foo() / \ / \ foo() B C foo() \ / \ / D foo()
ダイヤモンドの最上位の A クラスを削除して多重継承を許可したとしても、次のことがわかります。問題の曖昧さの側面。この理由を面接官に伝えると、なぜ C では多重継承がサポートできるのに、Java ではサポートできないのかと尋ねるでしょう。さて、この場合、私は以下に挙げる 2 番目の理由を彼に説明しようとします。これは技術的な困難によるものではなく、より保守性が高く、より明確な設計が推進要因です。ただし、これは Java 言語設計者によってのみ確認できます。推測しているだけです。 Wikipedia のリンクには、多重継承を使用するときにダイヤモンドの問題が原因で異なる言語アドレスの問題がどのように発生するかについて、いくつかのわかりやすい説明が記載されています。
2) 私にとって 2 番目の、そしてより説得力のある理由は、 多重継承は設計を複雑にし、変換やコンストラクターの連鎖などの際に問題を引き起こすということです。 多重継承が必要な状況はそれほど多くないと仮定すると、わかりやすくするために、多重継承を省略するのが賢明な判断です。さらに、Java はインターフェイスを使用した単一継承をサポートすることで、この曖昧さを回避できます。インターフェイスにはメソッド宣言のみがあり、実装は提供されていないため、特定のメソッドの実装は 1 つだけであるため、あいまいさは生じません。 (実践的かつ詳細な Java 面接質問集については、Java Zhiyin 公式アカウントの「面接質問集約」に返信してください)
同様に難しい Java の質問がもう 1 つあります。 C では演算子のオーバーロードがサポートされているのに、Java ではサポートされていないのはなぜですか? Java では文字列連結のために演算子がすでにオーバーロードされているという人もいるかもしれませんが、これらの引数に騙されないでください。
C とは異なり、Java は演算子のオーバーロードをサポートしません。 Java では、プログラマが 、 - 、 * 、 / などの標準算術演算子を自由にオーバーロードできるわけではありません。以前に C を使用したことがある場合、Java には C に比べて多くの機能が欠けています。たとえば、Java は多重継承をサポートせず、Java にはポインタがなく、Java には参照の受け渡しがありません。もう 1 つの同様の質問は、Java の参照渡しに関するもので、主に Java がパラメータを値または参照のどちらで渡すかを示しています。その背後にある本当の理由はわかりませんが、Java が演算子のオーバーロードをサポートしない理由という次の記述にはある程度の真実があると思います。
1) シンプルさと明確さ。 明瞭さは Java 設計者の目標の 1 つです。デザイナーは単に言語をコピーするのではなく、明確で真のオブジェクト指向言語を作りたいと考えていました。演算子のオーバーロードを追加すると、それを追加しない場合よりも確実に設計が複雑になり、コンパイラがより複雑になったり、演算子の実際の意味を特定するために余分な作業が必要になり、最適化の機会が減少するため、JVM の速度が低下する可能性があります。 Java での演算子の動作。
2) プログラミング エラーを回避します。 Java では、ユーザー定義の演算子のオーバーロードは許可されていません。プログラマが演算子のオーバーロードを許可すると、同じ演算子に複数の意味が与えられることになり、開発者の学習曲線が急勾配になり、物事がより混乱することになるからです。言語が演算子のオーバーロードをサポートすると、プログラミング エラーが増加し、その結果、開発と納品にかかる時間が長くなることが観察されています。 Java と JVM はガベージ コレクターを提供することで、メモリ管理に関して開発者の責任の大部分を既に担っているため、この機能はコードを汚染し、プログラミング エラーの原因となる可能性が高まるため、あまり意味がありません。
3) JVM の複雑さ。 JVM の観点から見ると、演算子のオーバーロードをサポートすると、問題がさらに難しくなります。メソッドのオーバーロードを使用すると、より直感的でクリーンな方法で同じことを実現できるため、Java で演算子のオーバーロードをサポートしないのは理にかなっています。比較的単純な JVM と比較して、複雑な JVM は JVM の速度を低下させ、Java での決定的な演算子の動作を保証するためにコードを最適化する機会が少なくなる可能性があります。
4) 開発ツールの取り扱いを容易にします。 これは、Java で演算子のオーバーロードをサポートしないことのもう 1 つの利点です。演算子のオーバーロードを省略すると、言語の操作が容易になり、その結果、IDE やリファクタリング ツールなど、言語を操作するためのツールの開発も容易になります。リファクタリング ツールは、C よりも Java の方がはるかに優れています。
私のお気に入りの Java 面接の質問は、難しいですが非常に役立ちます。インタビュアーの中には、なぜ Java では String が Final なのかという質問をよくする人もいます。
String オブジェクトは String プールにキャッシュされるため、Java では文字列は不変です。キャッシュされた文字列は複数の顧客間で共有されるため、1 人の顧客によるアクションが他のすべての顧客に影響を与えるリスクが常に存在します。たとえば、コードの一部が文字列「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 シリアル化 API は、java.io.Serializable および java.io.Externalizable インターフェイス、ObjectInputStream および ObjectOutputStream を通じてオブジェクトのシリアル化を処理する標準メカニズムを開発者に提供します。Java プログラマは、クラス構造の標準シリアル化に基づいて自由に選択できます。シリアル化されたバイナリ ファイル形式はクラス出力 API の一部となり、Java のプライベート プロパティとパッケージ可視プロパティのカプセル化が壊れる可能性があるため、後者が一般にベスト プラクティスと考えられています。シリアル化する
Java でクラスをシリアル化可能にするのは非常に簡単です。Java クラスは java.io.Serializable インターフェイスを実装するだけでよく、JVM はオブジェクト オブジェクトをデフォルト形式でシリアル化します。シリアル化可能には意図性が必要です。クラスをシリアル化可能にすることは長期的なコストになる可能性があるため、その実装の変更または変更が制限される場合があります。実装を通じてインターフェイスを追加してクラスの構造を変更する場合、フィールドの追加または削除により、フィールドが追加または削除される可能性があります。デフォルトのシリアル化を解除すると、カスタム バイナリ形式による非互換性の可能性を最小限に抑えることができますが、それでも下位互換性を確保するには多大な労力が必要です。シリアル化によりクラスを変更できる機能がどのように制限されるかの一例は、SerialVersionUID です。
SerialVersionUID を明示的に宣言しない場合、JVM はクラス構造に基づいてその構造を生成します。この構造は、クラス実装インターフェイスおよびその他の変更される可能性のあるいくつかの要因に依存します。新しいバージョンのクラス ファイルが別のインターフェイスを実装していると仮定すると、JVM は別の SerialVersionUID を生成し、古いバージョンのプログラムによってシリアル化された古いオブジェクトをロードしようとすると、InvalidClassException が発生します。
質問 1) Java のシリアル化可能インターフェイスと外部インターフェイスの違いは何ですか?これは、Java シリアル化のインタビューで最もよく聞かれる質問です。以下は私のバージョンです。 Externalizable は writeExternal() メソッドと readExternal() メソッドを提供します。これにより、Java のデフォルトのシリアル化に依存するのではなく、Java シリアル化メカニズムを柔軟に制御できるようになります。 Externalizable インターフェイスを適切に実装すると、アプリケーションのパフォーマンスが大幅に向上します。
質問 2) シリアル化可能なメソッドはいくつありますか?シリアル化可能なインターフェイスにメソッドがない場合、そのインターフェイスの目的は何ですか? Serializable Serializalbe インターフェイスは java.io パッケージに存在し、Java シリアル化メカニズムの中核を形成します。 Java ではタグ付きインターフェースとも呼ばれるメソッドはありません。クラスが java.io.Serializable インターフェースを実装すると、そのクラスは Java でシリアル化可能になり、Java シリアル化メカニズムを使用してこのオブジェクトをシリアル化するようにコンパイラーに指示します。
質問 3) SerialVersionUID とは何ですか?これを定義しないとどうなりますか? 私のお気に入りのインタビュー質問の 1 つは、Java シリアル化に関するものです。 SerialVersionUID は、プライベートの静的な最終的な Long ID です。オブジェクトに出力される場合、通常はオブジェクトのハッシュ コードです。JDK ツールの Serialver を使用して、シリアル化されたオブジェクトの SerialVersionUID を表示できます。 SerialVerionUID はオブジェクトのバージョン管理に使用されます。 SerialVersionUID はクラス ファイルで指定することもできます。 SerialVersionUID を指定しないと、クラス内のフィールドを追加または変更しても、新しいクラスと古いシリアル化オブジェクトに対して生成された SerialVersionUID が異なるため、シリアル化されたクラスは復元されません。 Java シリアル化プロセスは、オブジェクトの正しいシリアル化に依存して状態を復元し、シリアル化されたオブジェクトのシリアル バージョンが一致しない場合は java.io.InvalidClassException をスローします。serialVersionUID の詳細については、この記事 FQ を参照してください。必須。
質問 4) シリアル化する場合、一部のメンバーをシリアル化しないようにしますか?どうやって実装しますか? もう 1 つのよくある連載インタビューの質問。これは、一時変数とは何か、一時変数と静的変数はシリアル化されるのかなど、時々質問されることがあります。したがって、どのフィールドもオブジェクトの状態の一部にしたくない場合は、それを静的または一時的に宣言します。必要に応じて、これは Java シリアル化プロセスには含まれません。
質問 5) クラスのメンバーがシリアル化可能なインターフェイスを実装していない場合はどうなりますか?Java シリアル化プロセスに関する単純な質問です。 Serializable を実装するクラスのオブジェクトをシリアル化しようとしたが、そのオブジェクトに Serializable 以外のクラスへの参照が含まれている場合、実行時に NotSerializableException がスローされます。そのため、私は常に Serializable アラートを (コード内に) 入れています。コメント セクション) では、コード コメントのベスト プラクティスの 1 つでは、シリアル化可能なクラスに新しいフィールドを追加するときにこの事実に留意するように開発者に指示しています。
質問 6) クラスはシリアル化可能だが、そのスーパークラスはシリアル化できない場合、逆シリアル化後にスーパークラスから継承されたインスタンス変数の状態はどうなりますか?
Java シリアル化プロセスは、オブジェクト階層がシリアル化可能な構造である場合、つまり Java のシリアル化可能なインターフェイスが実装されている場合にのみ続行され、スーパー クラスから継承されたインスタンス変数の値は次のように構築されます。関数の初期化、逆シリアル化中にシリアル化できないスーパークラスの呼び出し。コンストラクタの連鎖は一度開始すると停止することができないため、上位階層のクラスがシリアライズ可能なインタフェースを実装していてもコンストラクタは実行されます。ステートメントからもわかるように、この連載インタビューの質問は非常にトリッキーで難しそうに見えますが、重要な概念を理解していれば難しくありません。
質問 7) シリアル化プロセスをカスタマイズすることは可能ですか、または Java のデフォルトのシリアル化プロセスをオーバーライドすることは可能ですか?
答えは「はい、できます」です。オブジェクトをシリアル化するには、ObjectOutputStream.writeObject(saveThisObject) を呼び出し、ObjectInputStream.readObject() を使用してオブジェクトを読み取る必要があることは誰もが知っていますが、Java 仮想マシンが提供するもう 1 つの機能は、これら 2 つのメソッドを定義することです。これら 2 つのメソッドがクラスで定義されている場合、JVM はデフォルトのシリアル化メカニズムを適用する代わりに、これら 2 つのメソッドを呼び出します。ここでは、あらゆる種類の前処理タスクまたは後処理タスクを実行することで、オブジェクトのシリアル化と逆シリアル化の動作をカスタマイズできます。
注意すべき重要な点は、継承、オーバーライド、またはオーバーロードを避けるために、これらのメソッドをプライベート メソッドとして宣言することです。 Java 仮想マシンのみがクラスのプライベート メソッドを呼び出すことができるため、クラスの整合性は維持され、Java シリアル化は適切に機能します。私の意見では、これは Java シリアル化面接で尋ねることのできる最良の質問の 1 つであり、それに続く良い質問は、なぜオブジェクトにカスタム シリアル化フォームを提供するのかということです。
質問 8) 新しいクラスのスーパークラスがシリアル化可能なインターフェイスを実装していると仮定すると、新しいクラスがシリアル化されないようにするにはどうすればよいですか?
#Java 連載における厳しいインタビューの質問。クラスのスーパー クラスが Java でシリアル化可能なインターフェイスを実装している場合、そのインターフェイスは Java ですでにシリアル化可能です。インターフェイスをキャンセルすることはできないため、実際にクラスをシリアル化不可能にすることはできませんが、新しいインターフェイスを回避する方法はあります。クラスのシリアル化。 Java シリアル化を回避するには、クラスに writeObject() メソッドと readObject() メソッドを実装し、このメソッドから NotSerializableException をスローする必要があります。これは、上記のシリアル化インタビューの質問で述べたように、Java シリアル化プロセスをカスタマイズすることのもう 1 つの利点であり、インタビューが進むにつれてフォローアップの質問として尋ねられることがよくあります。質問 9) Java のシリアル化および逆シリアル化プロセスではどのようなメソッドが使用されますか?
これは非常に一般的な面接の質問です。基本的に面接官はシリアル化について知りたいと考えています。「readObject()、writeObject()、readExternal()、および writeInitial() の使用法に精通していますか」。 Java のシリアル化は、java.io.ObjectOutputStream クラスによって行われます。このクラスは、シリアル化メカニズムを処理するために、下位レベルのバイト ストリームにカプセル化されたフィルター ストリームです。シリアル化メカニズムを通じてオブジェクトを保存するには、ObjectOutputStream.writeObject(savethisobject) を呼び出します。オブジェクトを逆シリアル化するには、ObjectInputStream.readObject() メソッドを呼び出します。 writeObject() メソッドを呼び出すと、Java でシリアル化プロセスがトリガーされます。 readObject() メソッドについて注意すべき重要な点は、永続性からバイトを読み取り、それらのバイトからオブジェクトを作成し、正しい型への型キャストを必要とするオブジェクトを返すために使用されることです。質問 10) シリアル化されて永続的に保存されるクラスがあり、そのクラスを変更して新しいフィールドを追加するとします。 シリアル化されたオブジェクトを逆シリアル化するとどうなりますか?
これは、クラスに独自のserialVersionUIDがあるかどうかによって異なります。上記の質問からわかるように、serialVersionUID を指定しない場合、Java コンパイラーがそれを生成し、通常、これはオブジェクトのハッシュ コードと等しくなります。新しいフィールドを追加すると、クラスの新しいバージョンに対して生成された新しい SerialVersionUID が、すでにシリアル化されたオブジェクトとは異なる可能性があります。この場合、Java Serialization API は java.io.InvalidClassException をスローします。コード内に独自のserialVersionUIDを設定し、それが単一クラス内で一定であることを確認することをお勧めします。11) Java シリアル化メカニズムにおける互換性のある変更と互換性のない変更は何ですか?
本当の課題は、シリアル化されたオブジェクトを使用してフィールドやメソッドを追加したり、フィールドやメソッドを削除したりして、クラス構造を変更することにあります。 Java シリアル化仕様によれば、フィールドまたはメソッドの追加は、互換性のない変更、クラス階層の変更、またはシリアル化可能なインターフェイスの非実装の対象となり、その一部は互換性のない変更の対象となります。互換性のある変更と互換性のない変更の完全なリストについては、Java シリアル化仕様を読むことをお勧めします。12) シリアル化されたオブジェクトをネットワーク経由で送信できますか?
はい、シリアル化されたオブジェクトはネットワーク経由で送信できます。Java のシリアル化されたオブジェクトは依然としてバイト形式であり、バイトをネットワーク経由で送信できるためです。シリアル化されたオブジェクトを BLOB としてディスクまたはデータベースに保存することもできます。13) Java シリアル化中にシリアル化されない変数はどれですか?
この質問は別の方法で尋ねられますが、目的は同じです。Java 開発者は静的変数と一時変数の詳細を知っていますか。静的変数はオブジェクトではなくクラスに属しているため、オブジェクトの状態の一部ではないため、Java シリアル化中に保存されません。 Java シリアル化ではオブジェクト自体ではなくオブジェクトの状態のみが保持されるためです。一時変数も Java シリアル化プロセスには含まれず、オブジェクトのシリアル化状態の一部ではありません。この質問をした後、インタビュアーはフォローアップとして、これらの変数の値を保存しない場合、これらのオブジェクトを逆シリアル化して再作成すると、これらの変数の値はどうなるのかを尋ねます。これは考慮する必要があることです。
もう 1 つの厄介なコア Java 問題です。待ってから通知してください。 wait およびmodifyは、waitまたはnotify-getが呼び出されるオブジェクトを監視する必要があるため、これらはsynchronized-markedメソッドまたはsynchronizedブロックで呼び出されます。
ほとんどの Java 開発者は、オブジェクト クラスの wait()、notify()、notifyAll() メソッドを Java の同期メソッドまたは同期ブロックで呼び出す必要があることを知っていますが、なぜそうするのか、何度考えたことでしょう。 wait、notify、notifyAll は、Java の同期ブロックまたはメソッドから来ていますか?
最近、Java のインタビューで友人にこの質問がされました。彼はそれについて考えてこう答えました。同期されたコンテキスト Java で wait() または Notice() メソッドを呼び出すと、Java で IllegalMonitorStateException を受け取ります。
彼の答えは実際には正しいですが、面接官はこの答えに完全には満足せず、問題を説明したいと考えています。インタビューの後、彼と私は同じ問題について話し合ったので、Java の wait() と Notice() の間の競合状態について面接官に伝えるべきだと思いました。この競合状態は、同期されたメソッドまたはブロックで呼び出さない場合に存在する可能性があります。
Java プログラムで競合状態がどのように発生するかを見てみましょう。これは、スレッド化された面接の人気の質問の 1 つでもあり、電話と対面の Java 開発者インタビューで頻繁に取り上げられます。したがって、Java の面接の準備をしている場合は、このような質問に備えておく必要があります。非常に役立つ書籍の 1 つが、『Java Programmer Interview Formula Book』です。これは、コア Java、マルチスレッド、IO と NIO、Spring や Hibernate などのフレームワークなど、Java インタビューの重要なトピックをほぼすべてカバーしている珍しい本です。ここで確認できます。
Java の同期メソッドから wait メソッドを使用する理由 Java の同期ブロックまたはメソッドからメソッドを呼び出す必要があるのはなぜですか? Java でのスレッド間通信には主に wait()、notify()、または NoticeAll() メソッドを使用します。スレッドは条件をチェックした後に待機します。たとえば、古典的なプロデューサー/コンシューマー問題では、バッファーがいっぱいの場合、プロデューサー スレッドは待機し、要素またはスレッドを使用してバッファー内にスペースを作成した後、コンシューマー スレッドがプロデューサーに通知します。 Notice() または NotifyAll() メソッドを呼び出して、条件が変更されたことを単一または複数のスレッドに通知します。通知スレッドが同期ブロックを離れると、待機中のすべてのスレッドが待機中のオブジェクト ロックの取得を開始し、幸運なスレッドがロック後に再取得します。 、wait() メソッドから戻って続行します。
操作全体をいくつかのステップに分けて、Java の wait() メソッドと notify() メソッドの間で競合状態が発生する可能性を確認します。シナリオをよりよく理解するために、Produce Consumer スレッドの例を使用します。
プロデューサー スレッドは、条件 (バッファーが完了しているかどうか) をテストし、待機する必要があることを確認します (バッファーがいっぱいであることを確認します)。
コンシューマ スレッドは、バッファ内の要素を消費した後に条件を設定します。
Consumer スレッドは、notify() メソッドを呼び出しますが、Producer スレッドはまだ待機していないため、これは聞こえません。
プロデューサー スレッドは wait() メソッドを呼び出し、待機状態に入ります。
そのため、競合状態により通知が失われる可能性があります。バッファを使用したり、要素を 1 つだけ使用したりすると、運用スレッドが永久に待機し、プログラムがハングします。 「Java 同期での Notice と Notifyall を待機しています。次に、この潜在的な競合状態を解決する方法を考えてみましょう?
この競合状態は、Java が提供する synchronized キーワードとロックを使用することで解決されます。 wait() を呼び出すには、 Java では、notify() または NoticeAll() のように、メソッドを呼び出すオブジェクトのロックを取得する必要があります。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 中国語 Web サイトの他の関連記事を参照してください。