Javaオブジェクトのメソッド-wait

すべてのオブジェクトの基本クラスとしての上、オブジェクトのjavaの存在のための安定した基盤を提供することが必要で、その存在は、メソッドを待って通知自明値、であるため、単純なことは、多くの場合、最も複雑な実装が含まれています保証を提供するために、コラボレーションをマルチスレッド。

ケース

public class WaitTestDemo {

    public static void main(String[] args) {
        Message msg = new Message("process it");
        Waiter waiter = new Waiter(msg);
        new Thread(waiter,"waiterThread").start();

        Waiter waiter1 = new Waiter(msg);
        new Thread(waiter1, "waiter1Thread").start();
        
        Notifier notifier = new Notifier(msg);
        new Thread(notifier, "notifierThread").start();
 
        System.out.println("All the threads are started");
    }

    public static class Message {
        private String msg;
        public Message(String str){
            this.msg=str;
        }
        public String getMsg() {
            return msg;
        }
        public void setMsg(String str) {
            this.msg=str;
        }
    }

    public static class Waiter implements Runnable{
        private Message msg;
        public Waiter(Message m){
            this.msg=m;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            synchronized (msg) {
                try{
                    System.out.println(name+" waiting to get notified at time:"+System.currentTimeMillis());
                    msg.wait();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(name+" waiter thread got notified at time:"+System.currentTimeMillis());
                //process the message now
                System.out.println(name+" processed: "+msg.getMsg());
            }
        }
    }

    public static class Notifier implements Runnable {
        private Message msg;

        public Notifier(Message msg) {
            this.msg = msg;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name+" started");
            try {
                Thread.sleep(1000);
                synchronized (msg) {
                    msg.setMsg(name+" Notifier work done");
                    msg.notify();
                    msg.notify();
                    //msg.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

出力:

All the threads are started
waiterThread waiting to get notified at time:1572344152693
waiter1Thread waiting to get notified at time:1572344152693
notifierThread started
waiterThread waiter thread got notified at time:1572344153705
waiterThread processed: notifierThread Notifier work done
waiter1Thread waiter thread got notified at time:1572344153706
waiter1Thread processed: notifierThread Notifier work done

あなたはまたのnotifyAllを使用することができ、出力は次のようになります。

All the threads are started
waiterThread waiting to get notified at time:1572344222162
waiter1Thread waiting to get notified at time:1572344222162
notifierThread started
waiter1Thread waiter thread got notified at time:1572344223175
waiter1Thread processed: notifierThread Notifier work done
waiterThread waiter thread got notified at time:1572344223177
waiterThread processed: notifierThread Notifier work done

最後のウェイクアップの順序を逆にすることを見つけます

通知方法を実行し、すぐに待機中のスレッドをウェイクアップしません通知方法の背後にある睡眠の効果を確認するためにいくつかのコードを追加し、スレッドのスリープ・5S場合は、この期間中にnotifyメソッドを実行した後、スレッドwaiterThread1はまだ、モニターを開催しますwaiterThreadだけ待機するように続けることができますスレッド。

なぜ使用は同期?

Javaでは、synchronized二つの形式、同期方法及び同期ブロックで使用されます。コードは以下の通りであります:

public class SynchronizedTest {

    public synchronized void doSth(){
        System.out.println("Hello World");
    }

    public void doSth1(){
        synchronized (SynchronizedTest.class){
            System.out.println("Hello World");
        }
    }
}

さんが使用してみましょうてjavapを上記のコードをコンパイルするために、次のような結果(部分的に役に立たない情報は除外):

public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

  public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/hollis/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String Hello World
        10: invokevirtual #4                  // 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: return

逆コンパイルは、私たちは私たちのために生成されたJavaバイトコードコンパイラを見ることができます。中の場合doSthdoSth1プロセスが少し異なります。それは言うことです。同期およびブロック同期コードを処理するための異なるJVM方法。

同期方法については、JVMが使用するACC_SYNCHRONIZED同期を達成するためのタグを。同期ブロックのために。JVMが使用しmonitorentermonitorexit同期を達成するために2つの命令を。

この部分では、JVMの仕様では、関連する記述を見つけることができます。

同期方法

ステージを同期させる方法が暗黙的です。定数プールの同期方法がありますACC_SYNCHRONIZEDサイン。スレッドは、メソッドにアクセスするときに、それが存在するかどうかをチェックするACC_SYNCHRONIZEDが設定されている場合は、あなたがモニターロックを取得する必要があり、および実行方法を開始し、メソッドが実行された後、モニタのロックを解除します。その後、他のスレッドがリクエストメソッドを実行する場合、モニターは生きるためにブロックするロックを取得することはできませんので。例外がメソッド投げ出される前に、メソッドの実行、異常の発生、および内部メソッドが例外を処理しない時に、モニターのロックが自動的に解除された場合、ことに留意されたいです。

シンクブロック

同期ブロックmonitorentermonitorexit2つの命令。Java(登録商標)仮想マシン仕様は、これらの2つのディレクティブについて紹介しています:

次のように実質的に:あなたが実行できるmonitorenter実行、ロックと理解指示にmonitorexitロックを解除するために理解していました。各オブジェクトは、回数カウンタの数のレコードがロックされて維持しています。カウンタのアンロックオブジェクトが1つのスレッドが(実行ロック取得した場合、ゼロであるmonitorenter後に)場合、再度同じスレッドを取得するオブジェクトのロック、カウンタは、カウンタの増分は再度、1にインクリメントされます。スレッドは(実行とロック解除するときにmonitorexit命令)カウンタが再びデクリメントします。カウンタがゼロのとき。ロックが解除され、他のスレッドがロックを獲得することができます。

概要

同期方法ACC_SYNCHRONIZEDキーワードの暗黙的な方法をロックします。実行スレッドの方法は、上にマークされるときACC_SYNCHRONIZED、あなたは最初の方法を実行するためにロックを取得する必要があります。

同期コードブロックによってmonitorenterおよびmonitorexitパフォーマンスロックされます。すると、スレッドの実行monitorenter最初のロックを取得する時、あなたは後者の方法を実行することができます。ときに、スレッドの実行monitorexit時間は、ロックを解除する必要があります。

各オブジェクトは、自己維持デジタルカウンタ0として表さこのカウンタロック時間は、任意のスレッドをロック得ることができます。カウンタがゼロでない場合、唯一のロックスレッドを取得するためには、再度ロックを取得することができます。彼らはロックを再入力することができます。

基本原理

そしてビルトインロックオブジェクトヘッダ(ObjectMonitor)

各オブジェクトは、3つの領域に分割されたオブジェクトヘッダ、実施例及びデータ整列パディング

  • オブジェクトヘッダは2つの部分から構成され、最初の部分は、ハッシュコード(ハッシュコード)として、ランタイム・データ・オブジェクト自体を格納するために、マーク・ワードで、GC世代年齢ロック状態フラグスレッドがロックを保持し、スレッドIDバイアス、バイアスタイムスタンプなど、1バイトのこの部分。第2の部分はクラースポインタ(ポインタ型)であり、オブジェクトは、そのクラスのメタデータへのポインタである仮想マシンがオブジェクト・クラスのインスタンスであるポインタによって決定され、また1バイトの一部です。オブジェクトが配列型である場合、バイト配列ストレージの必要な長さとして、3バイトオブジェクトヘッダを格納する必要があります
  • データの例には、それが配列の一部である場合、さらに、配列の長さを含むインスタンス情報は、親クラスの属性情報を含む属性、データクラスに格納され、このメモリ4バイトが整列します
  • 対象の仮想マシンの開始アドレスは、8バイトの整数倍でなければならないので、パディングデータが必要です。パディングデータは、バイトアライメントのために、存在する必要はありません。

レベルロック、ヘビーロック、GCマーカーは、以下の表に示すオブジェクトの記憶内容)に向かって付勢されてもよいです。
オブジェクトヘッダ構造を記憶します

シンクロナイズドは、多くの場合、ヘビー級のロックと呼ばが、1.6の後、最適化するための新しい軽量かつ偏ったロックロック、その上のヘビー級のロックフォーカスをした後、簡単な紹介の同期を最適化します。

オブジェクトヘッダの記憶内容から分かるように、第1の状態で、オブジェクトが保存されているロックが、前記軽量ロックヘビーロック、ロックフラグ10から膨張したとき、例外を同期しないでポインタポイントが監視しますオブジェクトは、開始アドレス(またチューブモニタ又はロックとも呼ばれます)。

事前にJavaオブジェクトに達成することは比較的簡単で同期が、フラグの変更、および開始アドレスモニタオブジェクトへのポインタは、その実装の焦点は、モニタオブジェクトです。

HotSpot仮想マシンでは、達成ObjectMonitorの使用を監視します。

内蔵ロック(ObjectMonitor)

一般に呼ば内蔵ロックオブジェクト、オブジェクトは、オブジェクトが(OpenJDKの内部参照)++ホットスポットCの底部に書き込まれているオブジェクトヘッダロックマークワードヘビーポインタを監視することで、コードをシンプルな外観。

//结构体如下
ObjectMonitor::ObjectMonitor() {  
  _header       = NULL;  
  _count       = 0;  
  _waiters      = 0,  
  _recursions   = 0;       //线程的重入次数
  _object       = NULL;  
  _owner        = NULL;    //标识拥有该monitor的线程
  _WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
  _WaitSetLock  = 0 ;  
  _Responsible  = NULL ;  
  _succ         = NULL ;  
  _cxq          = NULL ;    //多线程竞争锁进入时的单向链表
  FreeNext      = NULL ;  
  _EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
  _SpinFreq     = 0 ;  
  _SpinClock    = 0 ;  
  OwnerIsThread = 0 ;  
}  

ObjectMonitorキュー間の変換関係は次の図で表すことができます。
IMG

(_cxqキューが言うようになる)_waitSetと_EntryListで述べたように、その後、待機の一番下を見て、メソッドに通知

実装の方法を待ちます。

  //1.调用ObjectSynchronizer::wait方法
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  /*省略 */
  //2.获得Object的monitor对象(即内置锁)
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  //3.调用monitor的wait方法
  monitor->wait(millis, true, THREAD);
  /*省略*/
}
  //4.在wait方法中调用addWaiter方法
  inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  /*省略*/
  if (_WaitSet == NULL) {
    //_WaitSet为null,就初始化_waitSet
    _WaitSet = node;
    node->_prev = node;
    node->_next = node;
  } else {
    //否则就尾插
    ObjectWaiter* head = _WaitSet ;
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}
  //5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park  也就是wait

概要:オブジェクトは、内蔵のロック(objectMonitor)によって得られたオブジェクトにカプセル化し、内蔵スレッドロックOjectWaiter、その後、addWaiterに挿入して待機中のスレッドの_waitSetノードリンクリストにつながった、そして最終的にロックを解除されます。

この方法の基礎となる実装を通知します

  //1.调用ObjectSynchronizer::notify方法
    void ObjectSynchronizer::notify(Handle obj, TRAPS) {
    /*省略*/
    //2.调用ObjectSynchronizer::inflate方法
    ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
    //3.通过inflate方法得到ObjectMonitor对象
    ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
    /*省略*/
     if (mark->has_monitor()) {
          ObjectMonitor * inf = mark->monitor() ;
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ;
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
          return inf 
      }
    /*省略*/ 
      }
    //4.调用ObjectMonitor的notify方法
    void ObjectMonitor::notify(TRAPS) {
    /*省略*/
    //5.调用DequeueWaiter方法移出_waiterSet第一个结点
    ObjectWaiter * iterator = DequeueWaiter() ;
    //6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
    /**省略*/
  }

要約:オブジェクトが内蔵ロック(objectMonitor)によって得られる、内蔵ロックメソッド呼び出しは、リンクされたリスト内の最初のノードを削除し、それが行く_EntrySet入れ、ロックを取得するために待機しているノードを待っ_waitsetによって通知します。注:のnotifyAllは、ポリシーまたは_cxq _EntryListキューに応じて移動することができ、ここに住むません。

参照

/のはObject.wait JVMソースコード解析達成通知

[マルチスレッド(4つ)の深い理解 - Moniter実装原理]

ソースJVMから同期見えます

並行プログラミングは、メソッドの分析に通知を待ちます

おすすめ

転載: www.cnblogs.com/hongdada/p/11760765.html