Android 中級および上級開発面接の質問スプリント コレクション (2)

以下は、主に過去に収集されたインタビューの質問を分類およびソートするためのものであり、誰もが確認および参照するのに便利です。第二弾です〜

面接質問集第1弾はこちら: Android中級・上級開発面接質問スプリント集(1)

強調: [ 因篇幅问题:文中只放部分内容,全部文档需要的可找作者获取。]

Java コレクション

1. Java の List、Set、Map の違いについて教えてください。

参考回答:

リスト: 順序付き、反復可能 セット: 順序なし、単一要素、セット マップ: キーと値のペア

2. ArrayList と LinkedList の違いについて教えてください。

参考回答:

1. ArrayList は、連続したメモリ空間を割り当てる配列ベースのデータ構造です。データを追加する場合、要素を追加する場合、配列の容量を考慮する必要があるため、効率が低くなります。 ; データを削除する場合、連続したメモリ空間にあるため効率が悪いです. 最後の要素を削除することを除いて、任意の要素を削除すると、配列内の要素が移動します. 検索データは、インデックスのインデックスに従ってすばやく検索できます; 2 . LinkedList は連結リストベースのデータ構造です. 上記を比較してください. 利点: 必要ありません. 拡張と連続メモリスペースの問題を考慮すると, 追加と削除の効率が向上します.

3. HashMap と HashTable の違いを教えてください。

参考回答:

HashMap と Hashtable はどちらも Map インターフェースを実装していますが、どちらを使用するかを決定する前に、それらの違いを理解する必要があります。主な違いは、スレッド セーフ、同期、および速度です。

継承された親クラスが異なります

HashMap と Hashtable は作成者が異なるだけでなく、親クラスも異なります。HashMap は AbstractMap クラスから継承され、HashTable は Dictionary クラスから継承されます。ただし、それらはすべてマップ、Cloneable (コピー可能) および Serializable (シリアライズ可能) の 3 つのインターフェースを同時に実装します。

機能と長所と短所の比較:

  1. HashMap は Hashtable とほぼ同じですが、HashMap は同期されておらず、null を受け入れることができます (HashMap は null のキーと値を受け入れることができますが、Hashtable は受け入れられません)。
  2. HashMap は同期されておらず、Hashtable は同期されています。つまり、Hashtable はスレッドセーフであり、複数のスレッドが Hashtable を共有できます。適切な同期が行われない場合、複数のスレッドは HashMap を共有できません。Java 5 は、HashTable の代替であり、HashTable よりもスケーラブルな ConcurrentHashMap を提供します。
  3. もう 1 つの違いは、HashMap の Iterator はフェイルファスト イテレーターであるのに対し、Hashtable の列挙子イテレーターはフェイルファストではないことです。そのため、他のスレッドが HashMap の構造を変更 (要素の追加または削除) すると ConcurrentModificationException がスローされますが、イテレーター自体の remove() メソッドは要素を削除するときに ConcurrentModificationException をスローしません。ただし、これは保証された動作ではなく、JVM に依存します。これは、列挙型と反復子の違いでもあります。
  4. Hashtable はスレッドセーフで同期されているため、シングルスレッド環境では HashMap よりも遅くなります。同期が必要なく、単一のスレッドのみが必要な場合は、HashMap を使用する方が Hashtable よりも優れています。
  5. HashMap は、Map 内の要素の順序が時間の経過とともに変化しないことを保証しません。

内部実装および運用上の問題

ハッシュ表

  • 基になる配列 + リンクされたリストの実装。キーも値も nullにすることはできません。スレッドセーフです。スレッドセーフを実現する方法は、データを変更するときに HashTable 全体をロックすることですが、これは非効率的であり、ConcurrentHashMap は関連する最適化を行っています。
  • 初期サイズは11、拡張: newsize = olesize*2+1
  • インデックスの計算方法: index = (hash & 0x7FFFFFFF) % tab.length

ハッシュマップ

  • 基になる配列 + リンクされたリストの実装は、null キーと null 値を格納できます。スレッドは安全ではありません
  • 初期サイズは16、拡張: newsize = oldsize*2、サイズは 2 の n 乗でなければなりません
  • The Expansion is for the whole Map. 拡張が実行されるたびに、元の配列の要素が再計算され、順番に再挿入されます。
  • 要素を挿入後、展開するか否かを判断する 展開すると無効になる場合がある(挿入後に展開すると、再度挿入しないと展開無効となる)
  • Map 内の要素の総数が Entry 配列の 75% を超えると、展開操作がトリガーされます. リンクされたリストの長さを減らすために、要素の分布はより均一になります.
  • インデックスの計算方法: index = hash & (tab.length – 1)

HashMap の初期値では、負荷係数も考慮されます。

  • ハッシュ競合: いくつかのキーのハッシュ値が配列のサイズを法としている後, それらが同じ配列添え字に該当する場合, エントリーチェーンが形成されます. キーの検索は、エントリーチェーンの各要素をトラバースする必要があります. equals() 比較を実行します。
  • 読み込み係数: ハッシュ衝突の可能性を減らすために、デフォルトでは、HashMap のキーと値のペアが配列サイズの 75% に達すると、拡張がトリガーされます。したがって、推定容量が 100 の場合、配列サイズは 100/0.75=134 に設定する必要があります。
  • Space for time : キー検索時間を高速化したい場合は、負荷率をさらに減らし、初期サイズを増やしてハッシュ衝突の可能性を減らすことができます。

HashMap と HashTable はどちらもハッシュ アルゴリズムを使用して要素のストレージを決定するため、HashMap と Hashtable のハッシュ テーブルには次の属性が含まれます。

  • 容量: ハッシュ テーブル内のバケットの数
  • 初期容量 (初期容量): ハッシュ テーブルを作成するときのバケットの数。HashMap では、コンストラクターで初期容量を指定できます。
  • サイズ (サイズ): 現在のハッシュ テーブルのレコード数
  • 負荷率:負荷率は「サイズ/容量」と同じです。負荷係数 0 はハッシュ テーブルが空のことを意味し、0.5 はハッシュ テーブルが半分埋まっていることを意味します。軽負荷のハッシュ テーブルは衝突が少ないという特徴があり、挿入とクエリに適しています (ただし、Iterator を使用した要素の反復処理は低速です)。

また、ハッシュテーブルには「負荷制限」があり、「負荷制限」は0から1までの値で、「負荷制限」はハッシュテーブルの最大充填度を決定します。ハッシュ テーブルの負荷係数が指定された「負荷制限」に達すると、ハッシュ テーブルは自動的に容量 (バケットの数) を指数関数的に増やし、元のオブジェクトを新しいバケットに再分配します。これを再ハッシュと呼びます。

HashMap と Hashtable のコンストラクターでは、負荷制限を指定できます. HashMap と Hashtable のデフォルトの "負荷制限" は 0.75 です。これは、ハッシュ テーブルが 3/4 いっぱいになると、ハッシュ テーブルが再ハッシュされることを意味します。

「負荷制限」のデフォルト値 (0.75) は、時間とスペースのコストの妥協点です。

  • 「負荷制限」を高くすると、ハッシュ テーブルが占有するメモリ領域を減らすことができますが、データのクエリの時間オーバーヘッドが増加し、クエリが最も頻繁な操作になります (HashMap の get() メソッドと put() メソッドは両方ともクエリを使用します)。 )
  • 「負荷制限」を低くすると、データのクエリのパフォーマンスが向上しますが、ハッシュ テーブルが占めるメモリ オーバーヘッドが増加します。プログラマは、実際の状況に応じて「負荷制限」の値を調整できます。

結論は

Hashtable と HashMap には、スレッドの安全性と速度という大きな違いがいくつかあります。完全なスレッド セーフが必要な場合にのみ Hashtable を使用し、Java 5 以降を使用する場合は ConcurrentHashMap を使用します。

4. ArrayList の展開メカニズムについて教えてください。

参考回答:

初期容量は 10 で、11 番目の要素が追加されると容量は 1.5 倍に拡張され、16 番目の要素が追加されると容量は 15*1.5=22 に拡張されます。

5. HashMap はどのように機能しますか?

参考回答:

HashMapは、実際には「リンク リスト ハッシュ」データ構造、つまり、配列とリンク リストの組み合わせです。これは、ハッシュ テーブル ベースのMapインターフェイス。

  • 配列: ストレージ範囲は連続しており、大量のメモリを占有し、アドレス指定が容易で、挿入と削除が困難です。
  • リンクされたリスト:保存間隔は離散的で、メモリ占有は比較的緩く、アドレス指定は難しく、挿入と削除は簡単です。
  • Hashmapこの 2 つのデータ構造を総合的に適用することで、簡単なアドレッシングと簡単な挿入と削除を実現します。

たとえば、次の図を例として、HashMap の内部ストレージ構造
を見てみましょう。 HashMap のアクセス プロセスについては、次の図を参照してください。

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容; 
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③; 
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals; 
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤; 
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可; 
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

6. LinkedHashMap の動作原理と使用シナリオを簡単に説明してください。

参考回答:

  • LinkedHashMap のソース コードを見ると、HashMap を継承し、Map インターフェイスを実装していることがわかります。つまり、LinkedMap が持つ HashMap メソッドです。LinkHashMap と HashMap の主な違いは次のとおりです。LinkedHashMap は順序付けられていますが、hashmap は順序付けられていません。LinkedHashMap は、二重にリンクされたリストを維持することによって順序を達成します。これはまさに、このリンクされたリストを維持するために、メモリのオーバーヘッドが大きくなるためです。

  • 秩序と無秩序を加える

    • 無秩序とは、挿入の順序と出力の順序が矛盾していることです。
  • リンクリスト構造とシーケンシャル構造を追加: 線形構造は、シーケンシャル構造とリンクリスト構造に分けられます。

    • シーケンシャル構造: メモリ内の完全に順序付けられたメモリです。したがって、インデックスを直接クエリすると、クエリするデータを見つけることができます. 速度は非常に高速です. 欠点は、挿入と削除が遅いことです. クラスが並んでいるとき(列)のようなもので、誰もが自分がどこにいるかを知っています. 先生が3番目の位置を言う限り、生徒はすぐに先生が自分を探していることに気づきました. このとき、クラスメートを 2 番目の位置に挿入する必要があるため、2 番目の位置から始まる各クラスメートの位置は +1 である必要があります。だから遅いです。
    • リンクリスト構造:ノードヘッドを介してノードの前のノードと次のノードを記録します(つまり、従来の二重リンクリスト、単一リンクリストは次のノードのみを記録し、循環リンクリストは次のノードです)最後のノードの最初のノードを指します)。リンクされたリスト構造が完全なメモリを必要とせず、挿入と削除は比較的高速ですが、クエリは比較的遅いのは、まさにこの関係のためです。ただし、ノードを維持する必要があるため、メモリのオーバーヘッドは比較的大きくなります。クラスが並んでいる時と少し似ています. 誰もが自分の位置を知りませんが、誰が自分の前にいて誰が後ろにいるのかを知っています. 同級生 b を c の前に挿入したい場合、同級生 c が前に a だったことを覚えている限り、今度は bb に変更して、彼が前に a で c が後ろにあることを覚えておいてください。なので挿入は早いと思います。類似のものを削除します。しかし、教師が場所で検索する場合、教師が探している番号が見つかるまで、最初の番号から数え始める必要があります。したがって、クエリは遅いです。

7. ConcurrentHashMap についての理解について話してください。

参考回答:

一般的な並行コレクションは、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque などです。並行コレクションは、jdk1.5 以降でのみ使用可能な java.util.concurrent パッケージにあります。

Java には、通常のコレクション、同期 (スレッドセーフ) コレクション、および並行コレクションがあります。通常、通常のコレクションは最高のパフォーマンスを発揮しますが、マルチスレッドの安全性と同時実行の信頼性は保証されません。スレッドセーフなコレクションは、コレクションに同期同期ロックを追加するだけであり、パフォーマンスが大幅に犠牲になり、同時実行の効率が低下します. 並行コレクションは、マルチスレッドの安全性を確保するだけでなく、複雑な戦略を通じて同時実行の効率を向上させます. ConcurrentHashMap の実装です.スレッドセーフな HashMap の. デフォルトの構造には initialCapacity および loadFactor 属性もありますが、追加の concurrencyLevel 属性があります. 3 つの属性のデフォルト値は 16, 0.75 および 16. 内部でロック セグメンテーション テクノロジーを使用して配列を維持します.ロック セグメントの. , Entity[] 配列はセグメント配列に格納されます. 内部ハッシュ アルゴリズムは、データを異なるロックに均等に分散します.

Put 操作: Synchronized はこのメソッドに追加されません. まず、key.hashcode に対してハッシュ操作を実行して、key のハッシュ値を取得します。ハッシュ操作のアルゴリズムもマップとは異なります. ハッシュ値に従って、対応する配列内のセグメント オブジェクト (ReentrantLock から継承) を計算して取得し、セグメント オブジェクトの put メソッドを呼び出して現在の操作を完了します. ConcurrentHashMap は、concurrencyLevel に基づいて複数のセグメントを分割してキー値を格納し、put 操作ごとに配列全体がロックされるのを回避します。デフォルトでは、並行処理中のブロッキング現象を可能な限り減らすために、最良の場合、16 個のスレッドがコレクション オブジェクトをブロックせずに同時に操作できるようになっています。

get(key) 最初に key.hashCode に対してハッシュ操作を実行し、その値に基づいて対応するセグメント オブジェクトを見つけ、その get メソッドを呼び出して現在の操作を完了します。Segment の get 操作は、最初に、ハッシュ値とオブジェクト配列のサイズから 1 を引いた値に対してビットごとの AND 操作を実行することにより、配列の対応する位置にある HashEntry を取得します。

このステップでは、オブジェクト配列のサイズの変更と、配列上の対応する位置の HashEntry によって不整合が生じる可能性があるため、ConcurrentHashMap はどのようにそれを保証するのでしょうか?

オブジェクト配列のサイズの変更は、プット操作中にのみ発生する可能性があります. HashEntry オブジェクト配列に対応する変数は揮発性型であるため、HashEntry オブジェクト配列のサイズが変更された場合、読み取り操作が可能であることが保証されます.最新のオブジェクト配列サイズを参照してください。

HashEntry オブジェクトを取得した後、それによって形成されたリンク リスト上のオブジェクトとその次の属性が変更されないことをどのように保証できますか?

この時点で、ConcurrentHashMap は単純な方法を採用しています。つまり、HashEntry オブジェクトの hash、key、および next 属性は final です。これは、HashEntry オブジェクトをリンク リスト ベースの中間または末尾に挿入する方法がないことを意味します。次の属性で。これにより、HashEntry オブジェクトが取得されたときに、次の属性に基づいて構築されたリンク リストが変更されないことが保証されます。

デフォルトでは、ConcurrentHashMap はストレージ用にデータを 16 セグメントに分割し、16 セグメントは独自の異なるロック セグメントを保持します. ロックは、volatile および HashEntry リンク リストの不変性に基づいて、コレクション オブジェクトを変更するための put や remove などの操作にのみ使用されます.読み取りのロック解除を実装しました。これらのメソッドにより、ConcurrentHashMap は、特に挿入や削除よりもはるかに頻繁に読み取られるマップに対して、優れた同時実行サポートを維持できます。また、ConcurrentHashMap が採用するこれらのメソッドは、Java メモリ モデルと同時実行メカニズムの深い理解の具現化としても説明できます。概要: ConcurrentHashMap は Hashmap よりもスレッドセーフであり、HashTable よりも効率的です。

Java マルチスレッド

1. Java でマルチスレッドを使用する方法は何ですか?

参考回答:

  • Thread クラスを継承し、run メソッドを記述します。
  • Runnable インターフェースを実装し、それを Thread(runnable) コンストラクターに渡します。
  • ExecutorService スレッド プールを介してマルチスレッドを作成する
  • FutureTask + Executors による戻り値によるマルチスレッド化

2.スレッドのいくつかの状態について教えてください。

参考回答:

1.状態を作成します。スレッド オブジェクトが生成されるとき、作成されるスレッドであるオブジェクトの start メソッドは呼び出されません。2. 準備完了状態。スレッド オブジェクトの start メソッドが呼び出されると、スレッドは準備完了状態になりますが、この時点ではスレッド スケジューラはスレッドを現在のスレッドとして設定しておらず、この時点では準備完了状態にあります。スレッドが実行された後、待機またはスリープから戻った後も準備完了状態になります。3. 運用状況。スレッド スケジューラは、準備完了状態のスレッドを現在のスレッドとして設定し、この時点で、スレッドは実行中状態になり、run 関数でコードの実行を開始します。4.ブロッキング状態。スレッドが実行中の場合、スレッドは一時停止されます。通常は、実行を続行する前に、特定の時間 (リソースの準備が整っているなど) が発生するのを待ちます。スリープ、サスペンド、待機、およびその他のメソッドは、スレッドのブロックを引き起こす可能性があります。5.死の状態。スレッドの run メソッドが終了するか、stop メソッドが呼び出されると、スレッドは終了します。終了したスレッドの場合、start メソッドを使用してスレッドを準備できなくなります。

3.マルチスレッドで同期を実現する方法は?

参考回答:

おそらくいくつかの方法で:

  1. volatile - いくつかの単純なロジックの下では可能です。
  2. 同期;
  3. reentrantLock;
  4. cas=compare and swap(set)、これは安全でないクラスです。
  5. ハンドラー (少し気が進まないが、彼の実装自体は上記のテクノロジーの一部に依存している)

4.スレッドのデッドロックについて言えば、スレッドのデッドロックを効果的に回避する方法は?

参考回答:

まず、デッドロックの定義

マルチスレッドとマルチプロセッシングにより、システム リソースの使用率が向上し、システムの処理能力が向上します。ただし、同時実行はデッドロックという新たな問題ももたらします。いわゆるデッドロックとは、複数のスレッドがリソースを奪い合うことで生じるデッドロック(待ち合わせ)のことで、外力がなければこれらのプロセスは先に進めません。

第二に、デッドロックの原因

  1. システム リソースの競合

通常、システム内の譲ることのできないリソースの数は、複数のプロセスを実行するニーズを満たすのに十分ではないため、実行中のプロセス中にテープ ドライブやプリンターなどのリソースをめぐって競合することにより、プロセスがデッドロックに陥ります。デッドロックを引き起こす可能性があるのは、譲ることのできないリソースをめぐる競合のみであり、プリエンプティブルなリソースをめぐる競合がデッドロックを引き起こすことはありません。

  1. プロセスの事前注文は違法です

プロセスの実行中、リソースの要求と解放の順序が正しくないため、デッドロックが発生する可能性もあります。たとえば、並行プロセス P1 と P2 はそれぞれリソース R1 と R2 を維持しますが、プロセス P1 はリソース R2 に適用され、プロセス P2 はリソース R1 に適用されます。必要なリソースが占有されているため、両方がブロックされます。

3) セマフォの不適切な使用もデッドロックを引き起こす可能性があります。

プロセスは相互にメッセージを送信するのを待機し、その結果、これらのプロセスは先に進むことができません。たとえば、プロセス A はプロセス B によって送信されたメッセージを待っており、プロセス B はプロセス A によって送信されたメッセージを待っています。デッドロックを引き起こす他のリソース。

  1. デッドロックが発生するための必要条件

デッドロックが発生するには、次の 4 つの条件が同時に満たされる必要があり、いずれかの条件が成立しない限り、デッドロックは発生しません。

  • 相互排除条件: プロセスは、割り当てられたリソース (プリンターなど) の排他制御を必要とします。つまり、リソースは、一定期間、1 つのプロセスだけによって占有されます。このとき、別のプロセスがリソースを要求した場合、要求元のプロセスは待機することしかできません。
  • 非剥奪条件: プロセスによって取得されたリソースは、それが使い果たされる前に他のプロセスによって強制的に奪われることはありません。つまり、リソース自体を取得したプロセスによってのみ解放されます (アクティブに解放されることのみ可能です)。
  • 要求と保留の条件: プロセスは少なくとも 1 つのリソースを保持しているが、新しいリソース要求が行われており、リソースが他のプロセスによって占有されている. この時点で、要求元のプロセスはブロックされますが、取得したリソースは次のようになります。リリースされません。
  • 循環待機状態: プロセス リソースの循環待機チェーンがあり、チェーン内の各プロセスによって取得されたリソースは、チェーン内の次のプロセスによって同時に要求されます。
/** 
* 一个简单的死锁类 
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒 
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒 
* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定; 
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定; 
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。 
*/
public class DeadLock implements Runnable { 
  public int flag = 1; 
  //静态对象是类的所有对象共享的 
  private static Object o1 = new Object(), o2 = new Object(); 
  @Override
  public void run() { 
    System.out.println("flag=" + flag); 
    if (flag == 1) { 
      synchronized (o1) { 
        try { 
          Thread.sleep(500); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
        synchronized (o2) { 
          System.out.println("1"); 
        } 
      } 
    } 
    if (flag == 0) { 
      synchronized (o2) { 
        try { 
          Thread.sleep(500); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
        synchronized (o1) { 
          System.out.println("0"); 
        } 
      } 
    } 
  } 
  
  public static void main(String[] args) { 
      
    DeadLock td1 = new DeadLock(); 
    DeadLock td2 = new DeadLock(); 
    td1.flag = 1; 
    td2.flag = 0; 
    //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。 
    //td2的run()可能在td1的run()之前运行 
    new Thread(td1).start(); 
    new Thread(td2).start(); 
  
  } 
} 

3.デッドロックを回避する方法

場合によっては、デッドロックを回避できます。デッドロックを回避するための 3 つの手法:

  1. ロック順序 (スレッドは特定の順序でロックされます)
  2. ロック制限時間(スレッドがロックを取得しようとすると一定の制限時間が加算され、制限時間を超えるとロックの要求が破棄され、所有していたロックが解放されます)
  3. デッドロック検出

ロックオーダー

複数のスレッドが同じロックを必要とするが、それらを異なる順序で取得すると、デッドロックが発生しやすくなります。

すべてのスレッドが確実に同じ順序でロックを取得できる場合、デッドロックは発生しません。次の例を検討してください。

Thread 1:
  lock A 
  lock B
​
Thread 2:
   wait for A
   lock C (when A locked)
​
Thread 3:
   wait for A
   wait for B
   wait for C

スレッド (スレッド 3 など) がいくつかのロックを必要とする場合、特定の順序でロックを取得する必要があります。シーケンス内の前のロックを取得した後でのみ、後続のロックを取得できます。

たとえば、スレッド 2 とスレッド 3 は、ロック A を取得した後にのみロック C の取得を試みることができます。スレッド 1 はすでにロック A を所有しているため、スレッド 2 と 3 はロック A が解放されるまで待機する必要があります。次に、B または C をロックしようとする前に、A を正常にロックする必要があります。

シーケンシャル ロックは、効果的なデッドロック防止メカニズムです。ただし、この方法では、使用される可能性のあるすべてのロックを事前に知っておく必要があります (翻訳者注: これらのロックを適切に並べ替える必要があります) が、常に予測できない場合があります。

ロック時間制限

デッドロックを回避するもう 1 つの方法は、ロックを取得しようとするときにタイムアウトを追加することです。つまり、スレッドがロックを取得しようとしている間にこの時間制限を超えると、スレッドはロック要求を放棄します。スレッドが指定された制限時間内に必要なすべてのロックを正常に取得できない場合、スレッドはロールバックして取得したすべてのロックを解放し、ランダムな時間待機してから再試行します。このランダムな待機時間により、他のスレッドはこれらと同じロックを取得しようとする機会が与えられ、ロックが取得されない場合でもアプリケーションは実行を継続できます (前のロックのロジックに戻って繰り返します)。

2 つのスレッドが同じ 2 つのロックを異なる順序で取得しようとし、タイムアウトが発生した後にフォールバックして再試行するシナリオの例を次に示します。

Thread 1 locks A
Thread 2 locks B
​
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
​
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.
​
Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.

上記の例では、スレッド 2 はスレッド 1 の 200 ミリ秒前にロックを再試行するため、最初に両方のロックを正常に取得できます。この時点で、スレッド 1 はロック A を取得しようとし、待機状態になります。スレッド 2 が終了すると、スレッド 1 も両方のロックを正常に取得できます (スレッド 1 が 2 つのロックを正常に取得する前に、スレッド 2 または別のスレッドがロックの一部を取得しない限り)。

ロックのタイムアウトにより、このシナリオがデッドロックであるとは想定できないことに注意してください。また、ロックを取得したスレッド (他のスレッドがタイムアウトする原因) がタスクを完了するのに長い時間がかかることが原因である可能性もあります。

さらに、リソースの同じバッチに対して同時に多数のスレッドが競合している場合、タイムアウトやフォールバック メカニズムがある場合でも、これらのスレッドが繰り返し試行してもロックを取得できない可能性があります。スレッドが 2 つしかなく、リトライ タイムアウトが 0 ~ 500 ミリ秒に設定されている場合、この動作は発生しない可能性がありますが、10 スレッドまたは 20 スレッドの場合は異なります。これらのスレッドが同じ再試行回数を待機する確率がはるかに高い (または問題が発生するほど近い) ためです。(翻訳者注: タイムアウトとリトライのメカニズムは同時に競合を回避するためのものですが、スレッド数が多い場合、2 つ以上のスレッドのタイムアウト時間が同じまたは近い可能性が高いため、たとえa 競技によるタイムアウトの後、同じタイムアウト期間のために同時に再試行を開始し、新しいラウンドの競技と新しい問題が発生します。)

このメカニズムには問題があります。Java では、同期ブロックのタイムアウトを設定できません。カスタム ロックを作成するか、Java 5 の java.util.concurrent パッケージのツールを使用する必要があります。カスタム ロック クラスの記述は複雑ではありませんが、この記事の範囲を超えています。以降の Java 同時実行シリーズでは、カスタム ロックの内容について説明します。

デッドロック検出

デッドロック検出は、主にシーケンシャル ロックが不可能で、ロック タイムアウトが実現できないシナリオで、より優れたデッドロック防止メカニズムです。

スレッドがロックを取得するたびに、スレッドおよびロック関連のデータ構造 (マップ、グラフなど) に記録されます。さらに、スレッドがロックを要求するたびに、このデータ構造にも記録する必要があります。

スレッドがロックの要求に失敗すると、スレッドはロック グラフを走査して、デッドロックが発生しているかどうかを確認できます。たとえば、スレッド A がロック 7 を要求しているが、この時点でロック 7 がスレッド B によって保持されている場合、スレッド A は、スレッド A が現在保持しているロックをスレッド B が要求したかどうかを確認できます。スレッド B にそのような要求がある場合は、デッドロックが発生しています (スレッド A はロック 1 を所有し、ロック 7 を要求します。スレッド B はロック 7 を所有し、ロック 1 を要求します)。

もちろん、デッドロックは一般に、2 つのスレッドが互いのロックを保持するよりも複雑です。スレッド A はスレッド B を待機しており、スレッド B はスレッド C を待機しており、スレッド C はスレッド D を待機しており、スレッド D はスレッド A を待機しています。スレッド A がデッドロックを検出するには、B によって要求されたすべてのロックを徐々に検出する必要があります。スレッド B によって要求されたロックから開始して、スレッド A はスレッド C を見つけ、次にスレッド D を見つけ、スレッド D によって要求されたロックがスレッド A 自身によって保持されていることを発見します。これは、デッドロックが発生したことを認識したときです。

以下は、4 つのスレッド (A、B、C、および D) 間のロックの所有と要求のグラフです。このようなデータ構造は、デッドロックの検出に使用できます。

では、デッドロックが検出された場合、これらのスレッドは何をするのでしょうか?

考えられる解決策は、すべてのロックを解放し、ロールバックして、ランダムな時間の後に再試行することです。これは単純なロック タイムアウトに似ていますが、ロック要求がタイムアウトしたためではなく、デッドロックが発生した場合にのみロールバックが発生する点が異なります。フォールバックや待機はありますが、多数のスレッドが同じロックのバッチを競合する場合、依然としてデッドロックを繰り返します (編集者注: 理由はタイムアウトと同様であり、競合を根本的に緩和することはできません)。

より良い解決策は、これらのスレッドに優先順位を付けて、そのうちの 1 つ (またはいくつか) がフォールバックし、残りのスレッドがデッドロックが発生していないかのように必要なロックを保持し続けるようにすることです。これらのスレッドに与えられた優先度が固定されている場合、スレッドの同じバッチの優先度が常に高くなります。この問題を回避するために、デッドロックが発生したときにランダムな優先度を設定できます。

5.スレッドのブロックの原因について教えてください。

参考回答:

1. スレッドは Thread.sleep(int n) メソッドを実行し、スレッドは CPU を放棄し、n ミリ秒間スリープしてから実行を再開します。

2. スレッドは同期コードを実行する必要がありますが、該当する同期ロックを取得できないため、ブロック状態に入り、同期ロックの取得後に操作を再開できます。

3. スレッドはオブジェクトのwait()メソッドを実行し、ブロッキング状態に入り、オブジェクトのnotify()またはnotifyAll()メソッドを実行した後にのみ、他のスレッドによって起床することができます。

4. スレッドが I/O 操作を実行したり、リモート通信を実行したりすると、関連するリソースを待機するため、ブロック状態になります。たとえば、スレッドが System.in.read() メソッドを実行するときに、ユーザーがコンソールにデータを入力しない場合、スレッドはユーザーの入力データが読み取られるまで待機してから read() メソッドから戻ります。リモート通信中、クライアントプログラムでは、以下の場合にスレッドがブロッキング状態になることがあります。

5. サーバーとの接続を確立するためのリクエストが行われると、つまり、スレッドがパラメーターを使用してソケットのコンストラクターを実行するか、ソケットの connect() メソッドを実行すると、スレッドはブロック状態に入ります. 接続が成功するまで、スレッドは、Socket のコンストラクタまたは接続から開始されません。() メソッドは戻ります。

6. スレッドがソケットの入力ストリームからデータを読み取るときに、十分なデータがない場合、十分なデータが読み取られるか、入力ストリームの最後に到達するか、例外が発生するまで、スレッドはブロッキング状態に入ります。入力ストリームからの読み取り () メソッドが戻るか中止します。入力ストリームに十分なデータ量は? スレッドによって実行される read() メソッドのタイプによって異なります。

int read(); 入力ストリームに 1 バイトあれば十分です。

int read(byte[] buff); 入力ストリームのバイト数がパラメータ buff 配列の長さと同じであれば、それで十分です。

String readLine(); 入力ストリームに文字列が 1 行あれば十分です。InputStream クラスには、フィルタリングされたストリームの BufferedReader クラスでのみ使用できる readLine メソッドがないことに注意してください。

7. スレッドがソケットの出力ストリームにデータのバッチを書き込むと、ブロック状態になることがあります. すべてのデータが出力されるか、例外が発生した後、出力ストリームの write() メソッドから戻るか、異常に中断されます。

8. Socket の setSoLinger() メソッドを呼び出して、Socket を閉じるための遅延時間を設定します。その後、スレッドが Socket の close メソッドを実行すると、基になる Socket が残りのすべてのデータを送信するか、またはそれを超えるまで、スレッドはブロック状態になります。 setSoLinger() メソッド close() メソッドから戻るまでの遅延時間。

6.スレッドの run() メソッドと start() メソッドの違いについて教えてください。

参考回答:

run() は、通常のメンバー メソッドと同様に、繰り返し呼び出すことができます。ただし、run メソッドが単独で呼び出された場合、子スレッドでは実行されません。

start() このメソッドは 1 回だけ呼び出すことができます。このメソッドを呼び出した後、プログラムは新しいスレッドを開始して run メソッドを実行します。注: start を呼び出した後、スレッドは実行可能な状態 (実行されていない) になり、cup タイム スライスが取得されると、run メソッドが実行されます。run メソッドが終了すると、スレッドはすぐに終了します。

7.同期キーワードと揮発キーワードの違いについて教えてください。

参考回答:

1. volatile の本質は、レジスタ (ワーキング メモリ) 内の現在の変数の値が不明であり、メイン メモリから読み取る必要があることを jvm に伝えることです; 同期は現在の変数をロックし、現在のスレッドのみがアクセスできます。変数であり、他のスレッドはブロックされます。2. volatile は変数レベルでのみ使用できます; synchronized は変数、メソッド、およびクラス レベルで使用できます. volatile は変数の変更の可視性のみを達成でき、原子性を保証できません; 同期は変数の変更の可視性と原子性を保証できます変数 3.volatile ではスレッド ブロックは発生しませんが、synchronized ではスレッド ブロックが発生する可能性があります。4. volatile とマークされた変数は、コンパイラによって最適化されません。synchronized とマークされた変数は、コンパイラによって最適化されます。

8.スレッドの安全性を確保するには?

参考回答:

複数のスレッドがインスタンス オブジェクトの値を共有する場合、安全なマルチスレッド同時プログラミングを検討する際に、次の 3 つの要素を保証する必要があります。

原子性 (同期、ロック)

順序付き (揮発性、同期、ロック)

可視性 (揮発性、同期、ロック)

もちろん、synchronized と Lock は、その瞬間に 1 つのスレッドだけが同期コードを実行することを保証するため、スレッドセーフであり、この関数も実装できますが、スレッドは同期的に実行されるため、効率に影響します。

以下は、3 つの要素の詳細な説明です。

原子性:つまり、1 つまたは複数の操作がすべて実行され、実行プロセスがいかなる要因によっても中断されないか、またはいずれの操作も実行されないかのいずれかです。

Java では、プリミティブ データ型の変数の読み取り操作と代入操作はアトミック操作です。つまり、これらの操作は、実行されるかどうかにかかわらず、中断できません。

可視性:複数のスレッドが同じ変数にアクセスすると、1 つのスレッドが変数の値を変更し、他のスレッドは変更された値をすぐに見ることができます。

共有変数が volatile によって変更されると、変更された値がすぐにメイン メモリに更新され、他のスレッドが共有変数を読み取る必要がある場合は、新しい値を読み取るためにメモリに移動します。

通常の共有変数が変更された後、それらがいつメイン メモリに書き込まれるかが不明であるため、通常の共有変数は可視性を保証できません.他のスレッドがそれらを読み取ると、メモリはこの時点でまだ古い値を保持している可能性があるため、可視性を保証することはできません. .

メイン メモリを更新する手順:現在のスレッドが他のスレッドの作業メモリ内のキャッシュ変数のキャッシュ ラインを無効に設定し、その後、現在のスレッドが変数の値をメイン メモリに更新し、キャッシュ ラインを更新します。更新が成功した後の新しいスレッドへの他のスレッドのメイン メモリ アドレス。他のスレッドが変数を読み取り、そのキャッシュ ラインが無効であることが判明すると、キャッシュ ラインに対応するメイン メモリ アドレスが更新されるのを待ってから、対応するメイン メモリに移動して最新の値を読み取ります。Ordered: プログラムが実行される順序は、コードの順序で実行されます。Java メモリ モデルでは、コンパイラとプロセッサは命令を並べ替えることができますが、並べ替えプロセスはシングルスレッド プログラムの実行には影響しませんが、マルチスレッドの同時実行の正確性には影響します。特定の「順序」は、volatile キーワードによって保証できます。並行プログラミングを扱う場合、プログラムが原子性、可視性、および順序付けを満たしている限り、プログラムにダーティ データの問題はありません。

9. ThreadLocal の使用方法と原理について教えてください。

参考回答:

ThreadLocal は、スレッド共有変数を保存するために使用されます。同じ静的な ThreadLocal に対して、異なるスレッドは、他のスレッドの変数に影響を与えることなく、独自の変数を取得、設定、および削除することしかできません。

1. ThreadLocal.get: ThreadLocal の現在のスレッド共有変数の値を取得します。

2. ThreadLocal.set: 現在のスレッド共有変数の値を ThreadLocal に設定します。

3. ThreadLocal.remove: ThreadLocal の現在のスレッド共有変数の値を削除します。

4. ThreadLocal.initialValue: ThreadLocal が現在のスレッドによって割り当てられていない場合、または現在のスレッドが remove メソッドを呼び出すだけの場合、get メソッドが呼び出され、このメソッドの値が返されます。

10. Java スレッドの notify メソッドと notifyAll メソッドの違いは何ですか?

参考回答:

通知の違い: ロックを待機しているスレッドの 1 つだけが起動されます。notifyAll: ロックを待機しているすべてのスレッドを起動します。notify はスレッドを起こしてロックを取得するので、notifyAll はすべてのスレッドを起こし、アルゴリズムに従ってロックを取得するスレッドを 1 つ選択しますが、最終的に 1 つのスレッドだけがロックを取得するのではないでしょうか。では、なぜ JDK はこれら 2 つのメソッドを作成する必要があるのでしょうか。これら 2 つの同期方法の本質的な違いは何ですか?

これも、オブジェクト内のロックのスケジューリングから始まります。

オブジェクトの内部ロック実際、各オブジェクトには、ロック プール (EntrySet) と (WaitSet) 待機プールの 2 つのプールがあります。

ロック プール: スレッド A が既にロックを取得しており、スレッド B がこの時点でロックを取得する必要がある場合 (たとえば、同期された変更されたメソッドを呼び出す必要があるか、同期された変更されたコード ブロックを実行する必要がある場合)。すでに占有されている場合、スレッド B はこのロックを待つことしかできず、その時点でスレッド B はこのロックのロック プールに入ります。待機プール: スレッド A がロックを取得した後、いくつかの条件が満たされなかった場合 (たとえば、プロデューサーがプロデューサー/コンシューマー モードでロックを取得し、キューがいっぱいであると判断した場合) を想定して、この時点でオブジェクト ロックを呼び出す必要がある場合、スレッド A はロックを放棄し、ロックの待機プールに入ります。別のスレッドがロックの通知メソッドを呼び出すと、特定のアルゴリズムに従って待機プールからスレッドが選択され、このスレッドがロック プールに入れられます。他のスレッドがロックの notifyAll メソッドを呼び出すと、待機プール内のすべてのスレッドがロック プールに入れられ、ロックをめぐって競合します。

ロック プールと待機プールの違い: 待機プール内のスレッドはロックを取得できませんが、ロックを取得する前に、ロック プールに入るために起動する必要があります。

11.スレッドプールとは何ですか? スレッドプールを作成するには?

参考回答:

スレッド プール: 名前が示すように、スレッドの束が集まっており、コア メンバーはスレッドです. スレッドは何をしますか? もちろん、それは仕事をします!スレッドプールはどうですか?もちろん!また、スレッド プールを使用する利点は何ですか? 例えば、あなたが会社の上司(プロセス)で、その会社がN個のタスクを受け取ったとします。

  • スレッド プールを使用しない場合: 処理するタスクごとに新しいスレッドを作成します。これは、各ジョブを実行するために 1 人を募集し (スレッドの作成)、終了後にすぐにスレッドを破棄することと同じです (スレッドの破棄)。明らかに問題です。
  • スレッドプールを利用する: アイドル状態の従業員 (タスク実行後に待機しているスレッド) がある場合とない場合、タスクが来たら、まず自分の下に関係のない従業員 (アイドル状態のスレッド) がないかどうかを確認します。そうでない場合は、タスクを待機させる (タスク キューイング) か、スレッドがアイドル状態になるのを待つか、タスクを処理する人を増やすか (新しいスレッドを作成する)、またはタスクの実行を拒否することができます。誰かがアイドル状態のときは、新しいタスクが来るのを待つことができます。長く待ちすぎると、彼を解雇できます (スレッド破壊)

スレッドプールを利用することで、スレッドの再利用を実現し、スレッド作成に必要なリソースを節約できることがわかりますが、分割可能なタスクに適した fork-join スレッドプール (タスクプリエンプティブスレッドプール) もあります。通常のスレッドと同じ. プールは似ていますが、そのタスクは「プリエンプト」できます: たとえば、以前は非常に人気があった何億もの米のジョブが N 個のスレッドに分割されているため、スレッドが所属する部分をカウントする可能性があります。さまざまな理由により、このスレッドに早期に追加されました.

  • 共通のスレッド プールが使用されている場合、スレッドは黙って監視するだけで、役に立ちません。
  • fork-join タイプのスレッド プールは、このスレッドのタスクが実行された後、一緒にカウントするのに役立ちます。

12. Java のいくつかの一般的なロックとその使用シナリオについて教えてください。

参考回答:

一般的なロック:

  • 同期
  • 再入可能ロック
  • ReentrantReadWriteLock
  • AtomicInteger

13.スレッドの sleep() メソッドと wait() メソッドの違いについて教えてください。

参考回答:

sleep は Thread クラスのメソッド、wait は Object クラスのメソッド、sleep はロックを解放しない、ロックを解放するのを待つ sleep は Synchronized を必要としない、wait は Synchronized を必要とする Sleep はウェイクアップする必要はなく、wait は必要とするwake up (wait(int time) を除く)

14.悲観的ロックと楽観的ロックとは何ですか?

参考回答:

ロックは、他の人が同時にリソースを変更している間に自分がリソースを変更することを防ぐために設定される排他的なリソースであり、結果として同時に 2 つの変更が行われます。

楽観的ロック: デフォルトでは、スレッドが共有リソースを単独で処理するとき、同時にリソースを変更することはありません. プロセスが完了し、最終的にメモリに書き込まれたときにのみ、リソースが変更されていないかどうかをチェックします.前です。読み取り/書き込みロックに似た読み取りロックは楽観的ロックです。

悲観的ロック: デフォルトでは、スレッドが共有リソースを単独で処理すると、同時にリソースが変更されるため、リソースは取得されるとすぐに直接ロックされ、他のスレッドは操作できなくなります。ロックは論理的に処理されます。同様に、synchronized キーワード、条件付きロック、データベース行ロック、テーブル ロックなどもペシミスティック ロックです。

15. Java でスレッドセーフなコレクションは何ですか? それぞれの特徴は?

参考回答:

  1. 初期のスレッドセーフなコレクション
  • Vector = すべてのメソッドと同期の ArrayList
  • HashTable = すべてのメソッドと同期された HashMap
  1. パッケージ ツール クラス
  • Collections.synchronizedXXX() は元のコレクションにロック オブジェクトを追加し、コレクション内の各メソッドはこのロック オブジェクトを介して同期されます。
  1. java.util.concurrent パッケージ
  • ConcurrentHashMap 1.7 セグメント ロック テクノロジー、1.8 テーブルの各行の最初の要素をロック
  • CopyOnWriteXXXX は書き込みロックを追加し、書き込み時にオブジェクト全体をロックし、読み取りを同時に実行できます
  1. 他の
  • スタックはベクトルを継承します

16. Java で Atomic クラスが使用されるのはなぜですか? その原則と欠点を分析してみてください。

参考回答:

1. 私たちはよく i++ 操作を使用します. これがスレッドセーフではないことは誰もが知っています. 現時点では, 同時実行操作を処理するために synchronized キーワードが通常使用されています. 同時実行量が少ない場合、synchronized のパフォーマンスは特に高くありません. jdk1.6以前は、同期は重量ロックでリソース競合に関係なく変数がロックされていましたが、jdk1.6以降はバイアスロックと軽量ロックが導入され、大幅に効率化されました。アトミック クラスは cas の考え方を使用しており、実際のリソース競合がある場合にのみリソースが消費され、アトミックは基盤となるハードウェア命令セットを介して実装されるため、同時実行量が大きくない場合にパフォーマンスが高くなります。

2. 主な原則は CAS (比較と交換) です。これには 3 つの値 (V、O、N) が含まれます。V はメモリ内の実際の値、O はスレッドにロードされる期待値、N は計算された値です。目標結果値, 目標結果値が計算されたら, V と O が等しいかどうかを比較する. 不等の場合は V が他のスレッドによって書き換えられていることを意味し, 次に V を O に再割り当てしてから, 目標値を再計算し, 上記の手順をもう一度繰り返します.これはスピン操作と呼ばれます。欠点:

  1. O と V の値は毎回比較されるため、ABA の問題があります. V が比較前に何度も書き換えられている場合、最終的な値は以前の V のままであり、この状況を単純に知ることは不可能です.最終的な V と O を比較します。
  2. アトミック操作は、共有変数に対してのみ実行できます。
  3. V と O が等しくない場合、スピン操作が必要であることがわかります。同時実行数が多く、リソースの競合が激しい場合、スピン操作の待機時間が非常に長くなり、パフォーマンスが低下します。現時点では、他のロックを使用する方が適切です

17. ThreadLocal の使用シナリオについて教えてください。Synchronized と比較した場合の機能は何ですか?

参考回答:

ただし、ThreadLocal と Synchronized はマルチスレッドに関連しています。しかし、ThreadLocal は、マルチスレッド化の際に各スレッドが独立して変数にアクセスするためのものです.スレッド間の変数値は互いに影響しません.内部的には ThreadLocalMap があり、キーは現在の ThreadLocal の弱参照であり、値変数値です。同期とは別の意味で、マルチスレッド化すると、同期ロックによって複数のスレッドが同時に実装され、変数/メソッドにアクセスできるスレッドは 1 つだけになります。

Java 仮想マシン

1. Java のガベージ コレクション メカニズムについて簡単に説明してください。

参考回答:

ガベージ コレクションのメカニズム: ヒープ メモリの特定の領域にオブジェクト参照がない場合、このメモリ領域はガベージになり、ガベージ コレクタがリサイクルするのを待って、

強制ガベージ コレクション: プログラムは、参照変数によって参照されていないオブジェクトのみを制御でき、そのリサイクルを完全に制御することはできません。System.gc(); Runntime.getTuntime().gc(); 上記の 2 つの方法は、システムにガベージ コレクションの実行を推奨しますが、システムはガベージ コレクションを実行しない場合があります。オブジェクトの復活は finalize() メソッドで実現できます

2.強い参照、弱い参照、弱い参照、ファントム参照とは何か、またそれらの違いを答えてください。

参考回答:

  • 強参照( StrongReference ) 強参照は、最も一般的に使用される参照です。オブジェクトに強い参照がある場合、ガベージ コレクターはそれを収集しません。メモリ空間が不足すると、Java 仮想マシンは OutOfMemoryError エラーをスローしてプログラムを異常終了させます。強い参照を持つオブジェクトを任意に再利用してメモリ不足の問題を解決するのではありません。
  • ソフト参照( SoftReference ) オブジェクトにソフト参照しかない場合、メモリ空間は十分であり、ガベージ コレクターはそれを再利用しません。メモリ空間が不十分な場合、これらのオブジェクトのメモリは再利用されます。オブジェクトは、ガベージ コレクターによって収集されない限り、プログラムで使用できます。ソフト参照を使用して、メモリに依存するキャッシュを実装できます (例を以下に示します)。ソフト参照は、参照キューReferenceQueueと組み合わせ. ソフト参照によって参照されるオブジェクトがガベージ コレクターによって再利用される場合、Java 仮想マシンはソフト参照を関連する参照キューに追加します.
  • 弱参照( WeakReference ) 弱参照と弱参照の違いは、弱参照のみを持つオブジェクトのライフサイクルが短いことです。ガベージ コレクタ スレッドが管轄するメモリ領域をスキャンする過程で、弱参照のみを持つオブジェクトが見つかると、現在のメモリ空間が十分かどうかに関係なく、そのメモリが回収されます。ただし、ガベージ コレクターは非常に優先度の低いスレッドであるため、弱い参照しか持たないオブジェクトは必ずしもすぐに発見されるとは限りません。弱参照は、参照キューReferenceQueueと組み合わせ. 弱参照によって参照されるオブジェクトがガベージ コレクションされる場合、Java 仮想マシンは、関連付けられた参照キューに弱参照を追加します.
  • ファントム リファレンス( PhantomReference ) "ファントム リファレンス" は、名前が示すように、仮想参照のようなものです. 他の参照とは異なり、仮想参照はオブジェクトのライフサイクルを決定しません. オブジェクトがファントム参照のみを保持している場合、参照がまったくないかのようにガベージ コレクターによっていつでも収集できます。ファントム参照は、主に、ガベージ コレクターによって回収されるオブジェクトのアクティビティを追跡するために使用されます。ファントム参照とソフト参照および弱い参照の違いの 1 つは、ファントム参照を参照キューReferenceQueueと組み合わせです。ガベージ コレクターがオブジェクトを再利用する準備が整ったときに、まだ仮想参照があることがわかった場合、オブジェクトのメモリを再利用する前に、それに関連付けられている参照キューに仮想参照を追加します。

3. JVM でのクラスのローディング メカニズムとローディング プロセスを簡単に説明してください。

参考回答:

Javaのクラスローディングメカニズム

Java 言語システムには、次の 3 つのクラス ローダーが付属しています。

  • Bootstrap ClassLoader の最上位ローディング クラスは、主にコア クラス ライブラリ、rt.jar、resources.jar、charsets.jar、および %JRE_HOME%\lib の下のクラスをロードします。もう 1 つの注意点は、jvm の起動時に -Xbootclasspath とパスを指定することで、Bootstrap ClassLoader の読み込みディレクトリを変更できることです。たとえば、java -Xbootclasspath/a:path は、指定されたファイルによってデフォルトのブートストラップ パスに追加されます。
  • Extention ClassLoader %JRE_HOME%\lib\ext ディレクトリにある jar パッケージとクラス ファイルをロードする拡張クラス ローダー。-D java.ext.dirs オプションで指定されたディレクトリもロードできます。
  • SystemAppClass とも呼ばれる Appclass Loader は、現在のアプリケーションのクラスパスにあるすべてのクラスをロードします。

これら 3 つのローダーの読み込み順序を知る必要があります。

  1. Bootstrap クラスローダー
  2. 拡張クラスローダー
  3. AppClassLoader

次に、この読み込み順序の特定の実行戦略、親委任メカニズムを知る必要があります。

クラスローダがクラスとリソースを検索するときは、「委譲モード」を使用します.クラスが正常にロードされたかどうかを最初に判断します.そうでない場合は、それ自体では検索せず、最初に親ローダーを検索し、次に再帰的に検索します. Bootstrap ClassLoader まで下ります。Bootstrap クラスローダーが見つかった場合は、直接戻ります。見つからない場合は、レベルごとに戻り、最後に自分自身に到達してこれらのオブジェクトを見つけます。このメカニズムは、親の委任と呼ばれます。

4. JVM、Dalvik、および ART の原則と相違点は何ですか?

参考回答:

1. JVM は、Java バイトコードを実行できる仮想マシンである Java 仮想マシンを指し、さまざまな実装があります。

2. Dalvik 仮想マシンは、Google が Android プラットフォーム用に設計した仮想マシンで、.dex 形式に変換された Java プログラムの実行をサポートします。JVMとの違い:

  • Dalvik はレジスタ ベース、JVM はスタック ベース
  • Dalvik には独自のバイトコードがあり、Java バイトコードは使用しません
  • Android 2.2 以降、Dalvik は JIT ジャストインタイム コンパイルをサポートしています。

3. ART 仮想マシンは現在の Android 仮想マシンであり、Dalvik とは異なります。

  • ART は Ahead-of-time AOT プリコンパイル テクノロジーを使用し、Dalvik は JIT インスタント コンパイル テクノロジーを使用します。
  • AOT プリコンパイルは、アプリケーションのインストール プロセス中にすべてのバイトコードをマシン コードにコンパイルします. アプリケーションの実行中にリアルタイムでコンパイルする必要はなく、直接呼び出すことができます.
  • JIT ジャストインタイム コンパイルは、アプリケーションの起動時にパフォーマンス分析を通じてコードの実行を最適化し、アプリケーションの実行中に、バイトコードがリアルタイムでマシン コードにコンパイルされます。

したがって、ART仮想マシンはプログラムの実行効率を向上させます;最初のインストールには事前コンパイルが必要なため、インストール時間はDalvikよりもわずかに長く、マシンコードは大きなスペースを占有し、アプリケーションはわずかに大きなスペースを占有します. .

5. Java のメモリ リサイクル メカニズムについて教えてください。

参考回答:

Java では、そのメモリ管理には、メモリ割り当て (Java オブジェクトの作成時) とメモリ リサイクルの 2 つの側面が含まれます。どちらも JVM によって自動的に行われます。これにより、Java プログラマーの学習の難しさが軽減され、C /C++ のような問題が直接回避されます。記憶操作。しかし、それはまさに、メモリ管理が完全に JVM の責任であるからです。

6. JMMとは?どうしたの?それを解決する方法は?

参考回答:

Javaメモリモデル: 共有メモリシステムにおけるマルチスレッドプログラムの読み取りおよび書き込み操作動作の仕様を定義します. Javaメモリモデルは, この並行プログラミングの問題を解決するために存在します. 解決方法: メモリモデルは主に2つの方法を採用しています.同時実行の問題を解決します。つまり、プロセッサの最適化を制限します。もう 1 つは、メモリ バリアを使用することです。これら 2 つのメソッドでは、Java の最下層で実際にいくつかのキーワードがカプセル化されており、ここで使用する必要があるのはそれだけです。並行プログラミングにおけるアトミック性の問題に関しては、Java の最下層は Synchronized メソッドをカプセル化して、メソッドとコード ブロック内の操作がアトミックであることを保証します。可視性の問題に関しては、Java の最下層は Volatile メソッドをカプセル化し、これが使用されます。 by 変更された変数は、変更直後にメイン メモリに同期されます。実際に並べ替え問題と呼ばれる順序付けの問題については、Volatile キーワードによって命令の並べ替えも禁止され、Synchronzed キーワードによって、同時に 1 つのスレッド操作のみが許可されることが保証され、当然のことながら順序付けが保証されます。

おすすめ

転載: blog.csdn.net/m0_64420071/article/details/127225422