「wait()関数」の並行シリーズ

このブログは「Java並行プログラミングの美しさ」の研究ノートです。

wait()関数

スレッドが共有変数wait()メソッドを呼び出すと、呼び出し元のスレッドはブロックされ、次のいずれかが発生するまで中断されます。

  1. 他のスレッドが呼び出すの方法を。该共享对象notify()notifyAll()
  2. 別のスレッドによって呼び出され该线程interrupt()メソッドInterruptedExceptionスレッドは例外をスローして戻ります。

もう1つの注意点は、wait()メソッドをwait()呼び出すスレッドがオブジェクトのモニターロックを事前に取得していない場合、メソッドを呼び出すときに呼び出し元のスレッドがIllegalMonitorStateException例外をスローすることです。

では、スレッドはどのようにして共有変数のモニターロックを取得できますか?

  1. synchronized同期コードブロックを実行するときは、共有変数をパラメータとして使用してください。

    synchronized(共享变量){
          
          
      	// doSomething
    }
    
  2. 共有変数メソッドを呼び出すと、メソッドはsynchronized装飾を使用します。

    synchronized void add(int a, int b){
          
          
      	// doSomething
    }
    

誤った航跡

スレッドは、notify()、notifyAll()を呼び出す他のスレッドから通知されない場合、中断される場合、またはタイムアウトを待機する場合でも、状態挂起ごとに変化する可运行(つまり、ウェイクアップされる)可能性があり虚假唤醒ます。これはいわゆるです。

アプリケーションの実行では誤ったウェイクアップが発生することはめったにありませんが、問題が発生する前に問題を防ぐために、ウェイクアップするスレッドの条件が満たされているかどうかを常にテストし、満たされていない場合は待機を続けます。つまり、このwait()方法はループで呼び出されますループを終了するための条件は、スレッドをウェイクアップするための条件を満たすことです。

// obj为共享变量
synchronized(obj){
    
    
  	while(该线程被唤醒的条件不满足){
    
    
      	obj.wait();
    }
}

生産者と消費者の簡単な例から理解を深めましょう。queue共有変数である次のコードに示すように、プロデューサースレッドは前者のメソッドを呼び出しqueueキーワードをwait()使用しsynchronizedて共有変数のqueueモニターロックを取得するため、wait()メソッドを呼び出してIllegalMonitorStateException例外はスローされません

現在のキューに空き容量がない場合、呼び出されqueuedwait()メソッドは現在のスレッド一時停止します。ループは、上記の虚假唤醒問題を回避するためにここで使用されます現在のスレッドが誤って起動されたが、キューにまだ空き容量がない場合、現在のスレッドはwait()メソッドを呼び出して自身を一時停止します。

synchronized(queue){
    
    
  	while(queue.size() == MAX_SIZE){
    
     // 这个就是唤醒的条件,当满的时候就是不满足
      	try{
    
    
          	// 挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取该锁,然后获取队列中的元素
          	queue.wait();
        }catch(Exception ex){
    
    
          	ex.printStackTrace();
        }
    }
  	// 空闲则生成元素,并通知消费者线程
  	queue.add(element);
  	queue.notifyAll();
}

synchronized(queue){
    
    
  	while(queue.size() == 0){
    
    
      	try{
    
    
          	// 挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取该锁,将生产元素放入队列
          	queue.wait();
        }catch(Exception ex){
    
    
          	ex.printStackTrace();
        }
    }
  	// 消费元素,并通知唤醒生产者线程
  	queue.take();
    queue.notifyAll();
}

上記のコードでは、プロデューサースレッドAが最初にロックをsynchronized取得するとqueue、要素を生成しようとする後続のすべてのスレッドと消費スレッドがブロックされ、モニターロックが取得された場所で一時停止されます。スレッドAはロックを取得した後、現在のキューがいっぱいであることを検出し、queue.wait()メソッドを呼び出して自分自身をブロックしてから、取得したqueueロックを解放します。

ここで、なぜロックを解除する必要があるのか​​考えてみてください。

他のプロデューサースレッドとすべてのコンシューマースレッドがブロックされて一時停止され、スレッドAも一時停止されているためにリリースされない場合、これは適切死锁状态です。ここで、スレッドAは、デッドロックに必要な条件の1つを破るために、それ自体を一時停止した後、共有変数のロックを解放します持有并等待原则スレッドAがロックを解放すると、他のプロデューサースレッドの1つとすべてのコンシューマースレッドがqueueロックを取得して同期ブロックに入り、デッドロック状態が解消されます。

もう1つの注意点は、現在のスレッドが共有変数wait()メソッドを呼び出した後、現在のスレッドが只会释放当前共享变量上的锁他の共有変数のロックも保持している場合、これらのロックは解放されないということです。例を見てみましょう:

/**
 * @Author Hory
 * @Date 2020/10/6
 */
public class testResource {
    
    
    // 创建资源
    private static volatile Object resourceA = new Object();
    private static volatile Object resourceB = new Object();


    public static void main(String[] args) throws InterruptedException{
    
    
        // 创建线程
        Thread threadA = new Thread(new Runnable(){
    
    
            public void run(){
    
    
                try{
    
    
                    // 获取共享资源resourceA的监视器锁
                    synchronized(resourceA){
    
    
                        System.out.println("threadA get resourceA lock");

                        // 获取共享资源resourceB的监视器锁
                        synchronized(resourceB){
    
    
                            System.out.println("threadA get resourceB lock");
                            // 线程A阻塞,并释放获取到的resourceA的锁
                            System.out.println("threadA release resourceA lock");
                            resourceA.wait();
                        }
                    }
                }catch(InterruptedException e){
    
    
                    e.printStackTrace();
                }
            }
        });

        // 创建线程
        Thread threadB = new Thread(new Runnable(){
    
    
            public void run(){
    
    
                try{
    
    
                    // 休眠1s
                    Thread.sleep(1000);
                    // 获取共享资源resourceA的监视器锁
                    synchronized(resourceA){
    
    
                        System.out.println("threadB get resourceA lock");
                        System.out.println("threadB try get resourceA lock");

                        // 获取共享资源resourceB的监视器锁
                        synchronized(resourceB){
    
    
                            System.out.println("threadA get resourceB lock");
                            // 线程B阻塞,并释放获取到的resourceA的锁
                            System.out.println("threadB release resourceB lock");
                            resourceA.wait();
                        }
                    }
                }catch(InterruptedException e){
    
    
                    e.printStackTrace();
                }
            }
        });

        // 启动线程
        threadA.start();
        threadB.start();
        // 等待两个线程结束
        threadA.join();
        threadB.join();
        System.out.println("main over");
    }
}

演算結果:

threadA get resourceA lock
threadA get resourceB lock
threadA release resourceA lock
threadB get resourceA lock
threadB try get resourceA lock

上記のコードでは、mainスレッドAとスレッドBが関数で開始さます。スレッドAが最初にロックを取得できるようにするために、スレッドBは1秒間スリープします。スレッドAは共有変数resourceAと共有変数のロックを取得します。 resourceBを連続して実行し、次にresourceAのメソッドを呼び出してwait()それ自体ブロックし、スレッドAはそれ自体ブロックした後にresourceAのロックを解放します。

スレッドBがスリープした後、最初にresourceAのロックを取得しようとします。スレッドAがwait()ロックを解放するメソッドを呼び出さなかった場合、スレッドBはブロックされます。スレッドAがresourceAのロックを解放すると、スレッドBはTheを取得します。 resourceAをロックしてから、resourceBのロックを取得しようとします。スレッドAはresourceAのwait()メソッドを呼び出すため、スレッドAは自身を一時停止した後にresourceBで取得したロックを解放しません。したがって、スレッドBはresourceBでロックを取得しようとするとブロックされます。

これは、スレッドが共有オブジェクトのwait()メソッドを呼び出すと、現在のスレッドは現在の共有オブジェクトのロックのみを解放し、現在のスレッドが保持している他の共有オブジェクトのモニターロックは解放されないことを証明します

おすすめ

転載: blog.csdn.net/weixin_44471490/article/details/108957966