サイケ同期の実装の基礎となる、あなたはインタビューの恐れは何ですか?

synchronized基本となる実装、多くのオンラインの記事があります。しかし、記事や著者の多くは、コードを見ていなかった、ちょうど他の記事に基づいて、オンライン要約コピーが作成され、それはいくつかのミスは避けられない、または多くのポイントは、私のような読者がより多くを望むような文は、達成されていない理由として、渡すことです。

記事のこのシリーズは、ホットスポットになるsynchronizedで勉強することを望んで、ロックは、軽量で、ヘビー級のロックロックをロックし、バイアスされたロックを含め、総合的な分析を実現する、ロックエスカレーションプロセスの原理とソースコード解析のロックを解除するsynchronized学生いくつかの助けの道を。

 

それは(それは主にC ++、JVM機序、JVMのアセンブリコードのデバッグや調理ではないために、恥ずかしい限り悔い改めのためのいくつかの時間がかかります)コードの実現を確認するために約2週間かかりました、synchronized関連するコードは基本的にそれを再度読み込まれ、そのさらに一般的には、現在、彼の疑惑を確認するために、JVMログを添加することを含む、synchronized本のより包括的かつ明確に理解しているが、それは限られており、詳細の一部は、いくつかの省略は、だけでなく、私を修正してください期待して避けられません。

この記事ではあろうsynchronized、それはエスカレーションが発生ロックするロックいくつかの形態、ロックプロセスをロックおよびロック解除の様々な形態を、ロック状態を運ぶためのオブジェクトヘッダを含む一般的な導入であると機構。この記事は、時間での処理の一部についての背景とコンセプトを紹介することを意図していることに注意してください、本体のみの場合を述べ、実装の詳細については、ランタイム分析の異なるブランチは、今後の記事で詳しく説明します。

私は、JVMのバージョンがjdk8u、特定のバージョン番号、およびコードを見ることができ、ここで参照すること。

同期のプロフィール

Javaは二つの基本的なセマンティック達成同期提供:synchronizedメソッドとsynchronizedブロックを、私たちはデモを見て:

パブリッククラスSyncTest { 
    公共ボイドsyncBlock(){ 
        (本){同期
            するSystem.out.println( "ハローブロック")。
        } 
    } 
    パブリック同期ボイドsyncMethod(){ 
        System.out.printlnは( "ハロー法")。
    } 
}

SyncTest.javaが時にクラスファイルにコンパイルする場合はsynchronized、キーワードとsynchronizedバイトコードは少し異なる方法で、我々はできるjavap -v コマンド情報に対応するJVMバイトコードのクラスファイルを参照してください、次のように、情報の一部は次のとおりです。

{ 
  公開ボイドsyncBlock(); 
    記述:V()
    フラグ:ACC_PUBLIC 
    コード:
      スタック= 2、地元約= 3、args_size = 1。
         0:aload_0 
         1:DUP。
         2:astore_1 
         3:// monitorenter命令はmonitorenterシンクブロックに入る。
         getstatic:4フィールド、ジャワ2 //#/ LANG /のSystem.out:Ljava / IO /のPrintStream; 
         7:LDCブロック#3 //文字列こんにちは。
         9:INVOKEVIRTUAL 4 //方法、Java(登録商標)#/ IO / PrintStream.println:(Ljava /ラング/。列;)V 
        12である:aload_1 
        13である:monitorexitシンクブロック//終了するmonitorexit命令
        14:GOTO 22は、
        17:astore_2。
        18:aload_1
         3:LDC#5 //文字列のhelloメソッド
        19:monitorexit // monitorexit指令退出同步块
        20:aload_2 
        athrow:21 
        22:リターン
      例外テーブル:
         タイプをターゲットにから
             任意4 14 17 
            の任意の17 20 17 
 

  公共同期ボイドsyncMethod(); 
    記述子:()Vの
    フラグ:ACC_PUBLIC、ACC_SYNCHRONIZED //添加了ACC_SYNCHRONIZED标记
    コード:
      スタック= 2、地元の人々が= 1、args_size = 1 
         0:getstatic#2 //フィールドのjava / LANG /のSystem.out:Ljava / IO /のPrintStream ; 
         5:INVOKEVIRTUAL#4 //メソッドのJava / IO / PrintStream.println:(Ljava /ラング/文字列;)V 
         8:リターン
 
}

中国語は、上記のコメントから見たのものすることができるsynchronizedキーワードの用語は、javacコンパイル時に、対応する生成monitorenter及びmonitorexit命令は、それぞれsynchronized二つがある入るとシンクブロックから出るmonitorexit命令理由:例外の場合を確保するためにロックを解除することができ、それはjavac暗黙ののtry-finallyブロックの同期を追加し、最終的に呼び出します。monitorexitロックを解除するため。以下のためsynchronizedの方法の目的で、javacために発生したACC_SYNCHRONIZEDJVMのメソッド呼び出しでキーワード、メソッドの呼び出しがされたACC_SYNCHRONIZED変更、それは最初のロックを取得しようとします。

両方のための基礎となるJVMでは、synchronizedセマンティクスが、実質的に同じことを達成するため、後に詳細な分析を選択します。

この記事は、分析することを目指していますのでsynchronized原則の実現を、したがって、問題のいくつかのためにその使用が行くことはありません、私は友人が見ることができるかわからないこの記事を

ロックのいくつかの形態

伝統的なロックが(それはヘビー級のロックを下回ると言うことです)、Linuxの上で使用されるシステムの同期機能に依存mutexミューテックス、基礎となる実装に最も依存futexで、futex私の以前の見ることができる記事を、これらの同期機能は、ユーザに関連していますモードとカーネルモードのコンテキストの切り替え、プロセススイッチング、高コスト。添加のためのsynchronized二つのスレッドにキーワードが、競争を実行しない複数のスレッド、またはクローズを交互に間違いなく効率は比較的低いであろう従来のロック機構を使用して行きました。

JDK 1.6、前synchronizedこうして現像剤を与え、唯一の伝統的なロック機構は、左synchronized他の同期メカニズム悪い性能印象に比べてキーを。

二つの新しいロック機構紹介JDK 1.6では:バイアスされ、ロックロックと軽量を、彼らは競争かによる伝統的なロック機構がもたらすの使用に現場で無競争の複数のスレッドのパフォーマンス・オーバーヘッドなしで問題を解決するために導入されています。

ロック機構のこれらのタイプを達成するために見る前に、私たちは、ロック機構各種の基礎となるヘッド、下のオブジェクトを理解するために開始します。

オブジェクトヘッダ

Javaで任意のオブジェクトをロックとして使用することができるので、加工対象物との間のマッピングが存在しなければならないと(たとえば、スレッドが待機しているロックを保持している現在のスレッドとして)情報、対応するロックを格納します。非常に直感的なアプローチは、グローバルマップを使用するようにマッピング関係を格納するために、いくつかの問題があるだろうということである:異なる行うには、セキュリティのスレッドをマッピングする必要がsynchronized互いに、パフォーマンスの低下影響を、また、同期オブジェクトと比較した場合、長い時間のために、マップはより多くのメモリを取ることがあります。

あなたがうまく共存情報とオブジェクトヘッダ情報をロックすることができますので、もしそう最善の方法は、この関係をマッピングすることで、オブジェクトヘッド自体も、いくつかのハッシュコードを持っているので、オブジェクトのヘッダに格納され、GC関連のデータです。

:JVMでは、オブジェクトに加えて、メモリ内のオブジェクトデータは、オブジェクトヘッダは、2つの情報の種類有し、通常のオブジェクトのために、自分の頭を持っているであろうmark wordとインジケータの種類。データ配列に加えて、配列の長さの点でレコードを有することになります。

オブジェクトのクラスオブジェクトのポインタへのポインタの種類、mark wordオブジェクトのハッシュコード、GC世代、年齢、ロック状態を保存します。32ビットシステムでは、mark word32ビットの長さ、64ビットシステムの64ビットの長さ。よりデータの格納形式は、32ビットシステム上の各状態について以下のフォーマットを固定されていない限られたスペースに格納することができます。

「実装の基礎となるサイケシンクロナイズド - はじめに」

私たちは、ロック情報は、オブジェクトに存在している見ることができるmark wordの。オブジェクトがバイアスロック状態(バイアス可能)である場合、mark word記憶装置は、バイアスされたスレッドIDであり、ロック状態が軽量である場合(軽量ロック)、mark word記憶されているスレッドのスタックへのポインタであるLock Recordポインタ、状態ときヘビーロック(inflated2 )、モニタオブジェクトヒープを指すポインタ。

ヘビーロック

ヘビー級のロックは、伝統的な意味でロックされ、私たちはしばしば、オペレーティング・システムの基礎となる同期メカニズムの使用は、Javaのスレッドの同期を実装することを言います。

ヘビーロック状態は、オブジェクトは、mark wordヒープ・オブジェクト・モニタへのポインタです。

cxq(下図のContentionList)、するentrylist、WaitSet、所有者:オブジェクトがモニターので、いくつかのキー・フィールドが含まれます。

どのcxq、するentrylist、WaitSetは、リンクされたリスト構造ObjectWaiterからあり、所有者がロックを保持しているスレッドを指摘しました。

「実装の基礎となるサイケシンクロナイズド - はじめに」

スレッドがロックを取得しようとしたときにロックが占有されている場合、スレッドは、キューのcxq尾にObjectWaiterオブジェクトにパッケージし、その後、現在のスレッドを一時停止します。ロックがロックを保持しているスレッドの前にリリースされた場合、すべての要素がするentrylistモバイルが行くとキュースレッドのするentrylistヘッドを覚ますcxqます。

スレッド内のシンクブロックを呼び出す場合Object#waitの方法、対応ObjectWaiter WaitSetするentrylistから糸を除去しに加え、その後ロックを解除します。スレッドがするentrylistに移動しますWaitSetからObjectWaiter対応、通知待っているとき。

これらは、ObjectMonitorオブジェクトなどの詳細の多くが含まヘビー級ロック・プロセスの単なるスナップショット、あるから来ますか?ロックは、尾や頭cxqするentrylistに要素を移動するためにリリースされたのはいつですか?ときnotfiy、ObjectWaiterするentrylistは、尾や頭に移動していますか?

具体的な詳細については、記事のヘビー級のロックを分析します。

軽量ロック

JVMの開発者は、ブロック内の同期コード、多くの場合、Javaプログラムが実行されていることが判明し、異なるスレッドには競争、コードシンクブロック別の実行ではありません。この場合は、ヘビー級のロックでそれは必要ありません。したがって、JVMは、軽量ロックの概念を導入しています。

シンクブロックは、スレッドを実行する前に、JVMは、スレッドのスタックフレーム内の電流を作成するLock Recordオブジェクトを格納するためのヘッダを含む、  mark word(公式と呼ばれるDisplaced Mark Word)とオブジェクトへのポインタ。次のセクションでは、右の図ですLock Record

「実装の基礎となるサイケシンクロナイズド - はじめに」

ロック手順

1.スレッドスタック作成Lock Recordobjロックされたオブジェクトに(上記チャートオブジェクト参照で)フィールドポイント。

2. CAS命令によって直接Lock Recordオブジェクトヘッダに格納されたアドレスmark wordオブジェクトが得られる軽量糸ロックの代わりに、成功した修正せずにロック状態にある場合、。それが失敗した場合は、手順3に進みます。

3.現在のスレッドが既にロックを保持している場合、ロックはこの再エントリの代表です。配置されたLock Record第一の部分(Displaced Mark Word)カウンタウェイトの役割を果たし、ヌルです。そして、終わりました。

4.ここまで拡張ヘビー級のロックの必要性、競争があることを示しています。

ロック解除プロセス

1.すべて見つけるためにスレッドスタックを通過するobj現在のオブジェクトのロックと同じフィールドをLock Record

2.場合再進入の代表であり、nullの場合、ヌルの設定が続行。Lock RecordDisplaced Mark Wordobj

3.場合CAS命令を用いて、非ヌル、オブジェクトヘッダが復元さ成功した場合は、それ以外の拡張ヘビー級のロックを続けます。Lock RecordDisplaced Mark Wordmark wordDisplaced Mark Word

バイアスされたロック

Javaは、コードが多くの二次会パッケージに適切にマルチスレッドで実行することができることを確実にするためにので、マルチスレッドの言語をサポートしている、基本ライブラリ、つまり、私たちはしばしばスレッドセーフは、として参加することを言うsynchronizedので、同期セマンティクス。しかし、実際のアプリケーションが実行されている中で、1つのスレッドのみに依存する同期メソッド呼び出しされる可能性があります。たとえば、次のデモ:

import java.util.ArrayList;
import java.util.List;

public class SyncDemo1 {

    public static void main(String[] args) {
        SyncDemo1 syncDemo1 = new SyncDemo1();
        for (int i = 0; i < 100; i++) {
            syncDemo1.addString("test:" + i);
        }
    }

    private List<String> list = new ArrayList<>();

    public synchronized void addString(String s) {
        list.add(s);
    }

}

在这个demo中为了保证对list操纵时线程安全,对addString方法加了synchronized的修饰,但实际使用时却只有一个线程调用到该方法,对于轻量级锁而言,每次调用addString时,加锁解锁都有一个CAS操作;对于重量级锁而言,加锁也会有一个或多个CAS操作(这里的’一个‘、’多个‘数量词只是针对该demo,并不适用于所有场景)。

在JDK1.6中为了提高一个对象在一段很长的时间内都只被一个线程用做锁对象场景下的性能,引入了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只会执行几个简单的命令,而不是开销相对较大的CAS命令。我们来看看偏向锁是如何做的。

对象创建

当JVM启用了偏向锁模式(1.6以上默认开启),当新创建一个对象的时候,如果该对象所属的class没有关闭偏向锁模式(什么时候会关闭一个class的偏向模式下文会说,默认所有class的偏向模式都是是开启的),那新创建对象的mark word将是可偏向状态,此时mark word中的thread id(参见上文偏向状态下的mark word格式)为0,表示未偏向任何线程,也叫做匿名偏向(anonymously biased)。

加锁过程

case 1:当该对象第一次被线程获得锁的时候,发现是匿名偏向状态,则会用CAS指令,将mark word中的thread id由0改成当前线程Id。如果成功,则代表获得了偏向锁,继续执行同步块中的代码。否则,将偏向锁撤销,升级为轻量级锁。

case 2:当被偏向的线程再次进入同步块时,发现锁对象偏向的就是当前线程,在通过一些额外的检查后(细节见后面的文章),会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,然后继续执行同步块的代码,因为操纵的是线程私有的栈,因此不需要用到CAS指令;由此可见偏向锁模式下,当被偏向的线程再次尝试获得锁时,仅仅进行几个简单的操作就可以了,在这种情况下,synchronized关键字带来的性能开销基本可以忽略。

case 3.当其他线程进入同步块时,发现已经有偏向的线程了,则会进入到撤销偏向锁的逻辑里,一般来说,会在safepoint中去查看偏向的线程是否还存活,如果存活且还在同步块中则将锁升级为轻量级锁,原偏向的线程继续拥有锁,当前线程则走入到锁升级的逻辑里;如果偏向的线程已经不存活或者不在同步块中,则将对象头的mark word改为无锁状态(unlocked),之后再升级为轻量级锁。

由此可见,偏向锁升级的时机为:当锁已经发生偏向后,只要有另一个线程尝试获得偏向锁,则该偏向锁就会升级成轻量级锁。当然这个说法不绝对,因为还有批量重偏向这一机制。

解锁过程

当有其他线程尝试获得锁时,是根据遍历偏向线程的lock record来确定该线程是否还在执行同步块中的代码。因此偏向锁的解锁很简单,仅仅将栈中的最近一条lock recordobj字段设置为null。需要注意的是,偏向锁的解锁步骤中并不会修改对象头中的thread id。

下图展示了锁状态的转换流程:

「実装の基礎となるサイケシンクロナイズド - はじめに」

另外,偏向锁默认不是立即就启动的,在程序启动后,通常有几秒的延迟,可以通过命令 -XX:BiasedLockingStartupDelay=0来关闭延迟。

批量重偏向与撤销

从上文偏向锁的加锁解锁过程中可以看出,当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point时将偏向锁撤销为无锁状态或升级为轻量级/重量级锁。safe point这个词我们在GC中经常会提到,其代表了一个状态,在该状态下所有线程都是暂停的(大概这么个意思),详细可以看这篇文章。总之,偏向锁的撤销是有一定成本的,如果说运行时的场景本身存在多线程竞争的,那偏向锁的存在不仅不能提高性能,而且会导致性能下降。因此,JVM中增加了一种批量重偏向/撤销的机制。

存在如下两种情况:(见官方论文第4小节):

1.一个线程创建了大量对象并执行了初始的同步操作,之后在另一个线程中将这些对象作为锁进行之后的操作。这种case下,会导致大量的偏向锁撤销操作。

2.存在明显多线程竞争的场景下使用偏向锁是不合适的,例如生产者/消费者队列。

批量重偏向(bulk rebias)机制是为了解决第一种场景。批量撤销(bulk revoke)则是为了解决第二种场景。

其做法是:以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时,class中的epoch的值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其mark word的Thread Id 改成当前线程Id。

当达到重偏向阈值后,假设该class计数器继续增长,当其达到批量撤销的阈值后(默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后,对于该class的锁,直接走轻量级锁的逻辑。

End

Javaがあるsynchronized、唯一のスレッドによって保持されたロックに対応する、三つの形式で軽量、ヘビーロックをロックし、バイアスされたロックスレッドが交互に異なるロックを保持し、マルチスレッドロック競合3例。アップグレード>シーケンスヘビー級ロック- - >軽量ロックの条件が満たされていない場合は、ロックは、ロックを押してする傾向があります。それが私たちの議論の範囲内ではない、ロックのJVMの種類もダウングレードすることができますが、条件が非常に厳しいです。Javaは、この記事では主にsynchronized、基本的な導入であることを後でより詳細な分析があるでしょう。

 

私は、Java、Redisを、MongoDBのは、MySQL、カバーし、独自のコレクションを必要とし、自由なJavaの詳細データ、30Gの合計をコンパイルしたダボは、飼育係、春の雲を高並行性と他のチュートリアルを配布し、
データ収集マイクロ手紙:wwee19947

おすすめ

転載: www.cnblogs.com/yuxiang1/p/11305546.html