Javaのマルチスレッドコア知識の深い理解:インタビューは終了している必要があります!

学生のほとんどは知っておくべき同期ロックは、一部の学生は、それがに来るときすることができ、揮発性および請負、優秀な学生は、前の基礎にすることができ、同期、揮発性の原則を言うだけでなく、請負や、ConcurrentHashMapのような一般に使用されるデータ構造、原則。

この記事では、私たちが助けたい、マルチスレッドの同時治療のさまざまなを要約します。

まず、なぜマルチスレッド同時実行の問題があります

変数と同時に、なぜ複数のスレッドのアクセス(読み書き)、問題がそこに複雑になりますか?

  1. Javaのメモリモデルが指定するすべての変数がメインメモリに格納され、各スレッドは、独自のワーキングメモリを持っています。

  2. スレッドで使用される変数のコピーのメインメモリコピーに保存されたワーキングメモリのスレッドは、スレッドが変数のすべての操作は、作業メモリ内にある必要がありますが、読んで、メインメモリに直接書き込むことはできません。

  3. スレッドはすぐにメインメモリに同期していない変数、ワーキングメモリコピーにメインメモリからの最初の変数、変数への書き込みに、アクセスします。

  4. 異なるスレッド間で直接他の変数のワーキングメモリにアクセスすることはできません、変数がスレッド間で渡されると同時に彼らのワーキングメモリとメインメモリ間でデータを必要としています。

二、Javaのメモリモデル(JMM)

Javaのメモリモデル(JMM)以下に示すように、ワーキングメモリ(ローカルメモリ)とメインメモリ間のデータ同期プロセスに作用し、何がそうするかを指定し、データ同期データ同期をすることができません。

マルチスレッド! あなたはこの1つだけで十分です見ています

三つの要因によって複雑第三に、

アトミック:一回の操作で、CPUが実行次に、スケジューリングの途中で中断することができない、すなわち、操作が中断されていない、または実行、またはしません。

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

注文:オーダーコードで実行されるプログラムの実行シーケンス。

第四には、どのように並行性の問題を解決するために行うには?(フォーカス)

次の異なるシナリオの同時組み合わせの問題を解決するための分析アプローチ。

1、揮発性

1.1揮発性の特性

視認性を確保、原子性を保証するものではありません。

揮発性の変数を書くとき1.、JVMはメインメモリにリフレッシュローカルメモリ変数を強制します。

2.この書き込み動作は、他のスレッドのキャッシュの無効化を引き起こし、他のスレッドが読んで、メインメモリから読み出されます。他のスレッドへの揮発書き込みリアルタイムの可視性。

命令の並べ替えを並べ替え命令を禁止することは、我々は特定の規則に従う必要があり、一種のにプログラム命令のパフォーマンスを最適化するために、コンパイラやプロセッサの手段を意味します:

1.ないリセット命令の順序の依存関係、例えばA = 1、B =、依存性が存在し、B、並べ替えされません。

2.シングルスレッドでの結果には影響を与えません。例えば:= 1、B = 2、C = A + Bこれらの3つの操作を、最初の二つの操作を並べ替えてもよいが、C = A + Bの結果が3であることを確認するように並べ替えられません。

1.2利用シナリオ

変数のため、書き込み動作を実行するための唯一のスレッドが、他のスレッドが読み出され、この時間は、揮発性変数で修飾することができます。

なぜ1.3ダブルロックシングルトン揮発使用するには?

public class TestInstance {

private static volatile TestInstance mInstance;

public static TestInstance getInstance{ //1
if (mInstance == ){ //2
synchronized (TestInstance.class){ //3
if (mInstance == ){ //4
mInstance = new TestInstance; //5
}
}
}
return mInstance;
}

交換Javaコミュニティへの参加を歓迎  に参加するにはここをクリック  

揮発性を使用しない場合は、以下の操作ステップに分けたときにコメント5新しいTestInstanceに実行スレッド、同時実行の問題があるでしょう。

  1. メモリを割り当て

  2. オブジェクトの初期化

  3. mInstanceは、メモリを参照してください。

、実行の順序は、命令再配置する場合は、この時点では132、第三の時間を実行し、単にスレッドBが来て、mInstanceが空でないと判断された注2、初期化されていないとして使用されるオブジェクトを実行します。命令の並べ替えを禁止する揮発性のキーワードを使用してそう。

1.4揮発性の原則

JVM揮発性メモリバリアを根底には3つの機能を提供しますメモリバリアを達成するために使用されています。

  1. これは、次の命令がメモリバリア命令の並べ替え前の位置に排出されないであろう、また後で命令メモリバリアの前に排出されることを保証する、すなわち、その前にメモリバリアコマンドフレーズを、実行時すべての操作が完了しています。

  2. それはすぐにメインメモリに書き込まれたキャッシュ変更操作を強制します

  3. 書き込み操作がCPUのキャッシュライン内の他の障害につながることができた後、書き込み、他のスレッドがメインメモリから読み出されます読んで。

揮発1.5の制限事項

揮発性のできる唯一の保証視認性はの原子性を保証することはできません他のスレッドから見えるライト動作をするが、書き込みに、同時に複数のスレッドの問題を解決できません。

2、同期

2.1同期の利用シナリオ

複数のスレッドが同時に変数を書きます。

チケットの販売など、チケットは100以上あり、かつ揮発性の変数の変更と私の投票は、問題がある場合は同時にウィンドウAウインドウBはそれぞれ、チケットを販売しました。

ウィンドウは、残りのチケットは100で取得するには、Bが100以上取得するチケット窓、販売Bが99になりながら、メインメモリへのリフレッシュバックになるために販売99、メインメモリにもセットバックされ、メインメモリは、それが最終投票がつながります99の代わりに、98。

この状況は、一般的に同期使用できますが、複数のスレッドが同時に書くとき、あること、揮発性の限界の前で言えば。

同じ時間を保証することができる同期、唯一つのスレッドは、メソッドまたはコードのブロックを実行することができます。 参加するには、ここをクリックし   Javaのマルチスレッド終了備考デボン文書交換のコミュニティに参加することを歓迎します

2.2同期の原則

public class SynchronizedTest {

public static void main(String[] args) {
synchronized (SynchronizedTest.class) {
System.out.println("123");
}
method;
}

private static void method {
}
}

この最初の上のjavacとコードし、その後は、バイトコードの一部として、Java(登録商標)P -v SynchronizedTest.classコマンドバイトコードを表示します

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class com/lanshifu/opengldemo/test/SynchronizedTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String 123
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: invokestatic #6 // Method method:V
26: return

4を参照してください:monitorenterと14:Monitore XI T、声明を印刷する途中。

時間同期ブロックは、monitorenter命令が最初に実行され、その後、ブロックは、同期コードが実行Monitore出る、シンクブロック内のコードを実行XI T命令を。

使用同期を同期さ、キーは、スレッドがダウンし続けるために、モニタを取得したときに、モニターオブジェクトの取得を監視する必要があることであるそうでない場合は同期キューを入力して、スレッドの状態はBLOCKとなり、1つのスレッドのみに得ることができることMonitoreを聞いたときに、モニター、XIのと呼ばれるトン、キューのスレッドチームがあり、モニターを取得します。詳細は、を参照してください。

https://でWWW .jianshu .COM / P / d53bf830fa09

各オブジェクトはカウンターを持っているスレッドがオブジェクトのロックを取得するときのリリースがロックで対抗します後、カウンタは限りロックカウンタが0より大きいと、他のスレッドのアクセスのみを待つことができ、1つインクリメントされます。

2.3同期ロックのエスカレーション

私たちは、同期は、おそらくヘビー級ロックですが、同期のためのJava1.6を得るために、それがために、Java1.6、それほど悪いわけではないいくつかのケースでは、さまざまな最適化した後に行うとリリースロックがパフォーマンスもたらした消費量を減らす理解しますバイアスロックと軽量ロックの導入。

バイアスロック:ほとんどの場合、マルチスレッドロックだけでなく、競争力がありませんが、常に同じスレッドで何度も取得し、低コストとバイアスロックの導入を可能にするためにロックを取得するためのスレッド。

スレッドAロックアクセスコードに加えて、同期ブロックは、現在のスレッドIDはオブジェクト・ヘッダに格納されている場合、スレッドが入ると、このコードブロック同期ロックの出口以降の添加は、ロックがロックし、再び放出を必要としない場合。

軽量ロック:スレッドBはまた、シンクブロックにアクセスできるかどうかの場合には偏っロックは、最初のスレッドIDの比較は同じではありませんが、ロックは、軽量、軽量にアップグレードし、スピンによってロックを取得します。

ヘビー級のロック:スレッドAとスレッドBが同時にアクセス同期コードブロック、軽量ロックロックはロックを取得するためにヘビー級、ヘビー級スレッドAにアップグレードする場合は場合は、スレッドBは状態ブロックに入るようにチームのために待つことができます。

2.4同期の欠点

  1. あなたは、ロック・タイムアウトを設定することはできません

  2. コードは、ロックを解除することはできません

  3. 簡単にデッドロックにつながります

3、ReentrantLockの

同期欠点上記のロック・タイムアウトを設定することはできませんとロックコードを解放することはできません、ReentranLockは、この問題を解決することができます。

ReentrantLockの持つ複数の条件変数とロック競争力の高い場所、より適切な、ReentrantLockのは、より柔軟な条件、スレッドの待機とウェイクアップ動作を提供し、条件ReentrantLockの複数のインスタンスを持つことができ、それは、よりスケーラブルです。

3.1 ReentrantLockのの使用

ロック和ロック解除

ReentrantLock reentrantLock = new ReentrantLock;
System.out.println("reentrantLock->lock");
reentrantLock.lock;
try {
System.out.println("睡眠2秒...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace;
}finally {
reentrantLock.unlock;
System.out.println("reentrantLock->unlock");
}

ロック要求のタイミング達成:のtryLock

public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock;
Thread thread1 = new Thread_tryLock(reentrantLock);
thread1.setName("thread1");
thread1.start;
Thread thread2 = new Thread_tryLock(reentrantLock);
thread2.setName("thread2");
thread2.start;
}


static class Thread_tryLock extends Thread {
ReentrantLock reentrantLock;
public Thread_tryLock(ReentrantLock reentrantLock) {
this.reentrantLock = reentrantLock;
}

@Override
public void run {
try {
System.out.println("try lock:" + Thread.currentThread.getName);
boolean tryLock = reentrantLock.tryLock(3, TimeUnit.SECONDS);
if (tryLock) {
System.out.println("try lock success :" + Thread.currentThread.getName);
System.out.println("睡眠一下:" + Thread.currentThread.getName);
Thread.sleep(5000);
System.out.println("醒了:" + Thread.currentThread.getName);
} else {
System.out.println("try lock 超时 :" + Thread.currentThread.getName);
}
} catch (InterruptedException e) {
e.printStackTrace;
} finally {
System.out.println("unlock:" + Thread.currentThread.getName);
reentrantLock.unlock;
}
}
}

ログを出力します:

try lock:thread1
try lock:thread2
try lock success :thread2
睡眠一下:thread2
try lock 超时 :thread1
unlock:thread1
Exception in thread "thread1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at com.lanshifu.demo_module.test.lock.ReentranLockTest$Thread_tryLock.run(ReentranLockTest.java:60)
醒了:thread2
unlock:thread2

上記はtrtLockの使用を示し、trtLockがロックを取得するための待機時間を設定し、3秒以上が失敗に直接戻るには、結果がログから見ることができます。スレッド1がロック解除を呼び出すべきではありません、ロックが失敗した取得するための例外があります。

3.2条件条件

public static void main(String[] args) {
Thread_Condition thread_condition = new Thread_Condition;
thread_condition.setName("测试Condition的线程");
thread_condition.start;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace;
}
thread_condition.singal;
}
static class Thread_Condition extends Thread {

@Override
public void run {
await;
}

private ReentrantLock lock = new ReentrantLock;
public Condition condition = lock.newCondition;

public void await {
try {
System.out.println("lock");
lock.lock;
System.out.println(Thread.currentThread.getName + ":我在等待通知的到来...");
condition.await;//await 和 signal 对应
//condition.await(2, TimeUnit.SECONDS); //设置等待超时时间
System.out.println(Thread.currentThread.getName + ":等到通知了,我继续执行>>>");
} catch (Exception e) {
e.printStackTrace;
} finally {
System.out.println("unlock");
lock.unlock;
}
}

public void singal {
try {
System.out.println("lock");
lock.lock;
System.out.println("我要通知在等待的线程,condition.signal");
condition.signal;//await 和 signal 对应
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace;
} finally {
System.out.println("unlock");
lock.unlock;
}
}
}

実行ログを印刷します

lock
测试Condition的线程:我在等待通知的到来...
lock
我要通知在等待的线程,condition.signal
unlock
测试Condition的线程:等到通知了,我继续执行>>>
unlock

上記の条件を示し、使用に信号を待って、最初のロックを提供します。

3.3フェアロックと非ロックフェア

ReentrantLockのコンストラクタは、公正ロックにtrueを渡します。

公平ロックは、一次への基礎を提供しています割り当てロックスレッド、ためにロックシーケンスを取得するためのスレッドを示します。フェアは、ロックをつかむためにロック機構ではありませんロックがランダムに取得され、いくつかのスレッドが同じロックを取得する可能性があり、それは公平ではありません。

3.4 ReentrantLockの上のご注意

  1. ReentrantLockの使用ロックとアンロックは取得してロックを解除するには

  2. ロックの正常または異常な操作が解除されるので、最終的にロック解除を置くために

  3. 条件を使用する前に、信号方式を待って、あなたが取得するオブジェクトモニターにロックメソッドを呼び出す必要があります

4、および契約

上記の分析を通じて、同時重症の場合、同時に一つだけのスレッドがロックを獲得することができますので、ロックの使用は、明らかに非効率的で、他のスレッドが唯一素直に待っていることができます。

Javaは、その後、いくつかの共通のデータ構造の同時袋を収縮し、この問題を解決する提供します。

4.1のConcurrentHashMap

HashMapのは、スレッドセーフではありませんことを、データ構造、ハッシュテーブルその後、HashMapの基礎、getおよびputメソッドプラススレッドセーフになるように変更同期方法が、下に効率が高い同時実行、最終的にはConcurrentHashMapの代わりに私たちは皆知っています。

ConcurrentHashMapの区分ロック、内部のデフォルトバレル16、取得、およびプット操作、最初のキーの計算のhashCode、その後、モジュール16、浴槽16の低下と、その後はReentrantLockので(ロック内の各バケットに追加されます)、バケット構造は、HashMapの(プラス長すぎる赤黒木をオンにする)リンクリストの配列です。

だから、理論的には同時に16件のスレッドのアクセスをサポートします。

4.2 LinkBlockingQueue

、内部ReentrantLockの複数のキューリンクリスト構造をブロック

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock;
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition;
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock;
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition;

private void signalNotEmpty {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock;
try {
notEmpty.signal;
} finally {
takeLock.unlock;
}
}

/**
* Signals a waiting put. Called only from take/poll.
*/
private void signalNotFull {
final ReentrantLock putLock = this.putLock;
putLock.lock;
try {
notFull.signal;
} finally {
putLock.unlock;
}
}

ソースはあまり固執しない、単にLinkBlockingQueueロジックを見て:

1.データがない場合は、キューからキューをデータを取得し、notEmpty.awaitを呼び出して、入るのを待っています。

データは、通話のキューに入れたとき2. notEmpty.signal;、1の終了を待って、消費者に通知し、ウェイクアップは継続します。

notFull.signalを起動したとき3.キューからデータを取り、生産を継続するために生産者に通知します。

この時間は、消費者が消費、あること、データをフェッチするために3を待ってnotFull.signalを送った; 4.データはデータキューが最大に達したと判断された場合、キューに入れ、その後、notFull.await呼び出しを待ちます生産者は生産を継続します。

LinkBlockingQueueは、典型的な生産者 - 消費者モデルであり、ソースコードの詳細については、より多くの言うことはありません。

:4.3原子カテゴリのAtomicIntegerの

内部で使用するCAS(比較とスワップ)保証アトミック

自己増力int型の一例として、

AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.incrementAndGet;//自增

ソースコードを見てください

/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet {
return U.getAndAddInt(this, VALUE, 1) + 1;
}

Uは安全でない、ルック#getAndA危険であるDDのInt

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}

compareAndSwapInt保証アトミックによります。

V.の概要

インタビューの質問はそう答えに、マルチスレッド尋ねました:

  1. 一つだけのスレッドの書き込み、他のスレッドが読み込まれると、揮発性の変数を変更することができます

  2. ので、通常の状況下では、その後、あなたは同期、同期の深刻な同時が始まるヘビー級のロックではないことができない場合には、複数のスレッドの書き込みには、そのような場合にのみ1スレッドへのアクセスなど全く同時重度の場合は、ロックをバイアスされていない。場合に、複数のスレッドのアクセスではなく、同じ訪問で、軽量ロックロックのエスカレーションとして今回、複数のスレッドが同じ時間にアクセスするときに、ヘビー級のロックのために、この時間をアップグレードしてください。したがって、同時実行の場合には同期が可能である使用して、非常に深刻ではありません。シンクロナイズドが、制限があり、ロック・タイムアウトを設定することができないように、コードがロックを解除することはできません。

  3. ReentranLockコードがロックを解除することができ、ロックアウトを設定することができます。

  4. 多くのスレッドがある場合は、一度に一つのスレッドは、同期ブロックに入ることができるので、高い同時実行、同期、ReentranLock低効率、下で同時にアクセスするので、他のスレッドがロックを待っています。これを使用することができる場合のようなデータ構造と契約、例えばConcurrentHashMapの、LinkBlockingQueue、および原子データ構造:のAtomicInteger。

ときインタビューは基本[OK]を答えるために、この考え方に基づいて上記の要約しました。今の契約が来ると、その後、ConcurrentHashMapのに加えて、いくつかの他の一般的に使用されるデータ構造の原理はまた次を理解する必要があり、たとえば、HashMapのために、ハッシュテーブル、TreeMapの原理は、ArrayListに、LinkedListのコントラスト、これらは自分のために、ソースコードやブログのいくつかを見る、一般的なものです。

それはインタビューに対処するためであればマルチスレッドについて最初あまりない問題であることを準備する必要があり、この記事のラインに沿って、ここにまとめます。

JAVA同時ナレッジベース、Javaスレッドの実装/作成モード、スレッドプールのスレッドのライフサイクル(状態)の4種類、糸4つの方法、睡眠を終了し、この小さなシリーズでは、文書の内容を含め、すべての人のために一緒にマルチスレッドの文書を置きます待機差、差RUN、JAVAのバックグラウンドスレッド、JAVAロック、スレッドの基本的な方法で、スレッドコンテキストの切り替え、同期ロックデッドロック、スレッドプールの原則で始まり、

ディレクトリの小さなシリーズの一部を示し、スペースの制限だけで、あなたは小さなマルチスレッドパートナーの詳細な研究をしたいです

マルチスレッド

スレッドプールの4種類

公開された50元の記事 ウォン称賛16 ビュー2564

おすすめ

転載: blog.csdn.net/kxkxyzyz/article/details/104029379