同期で待機/通知メソッドを使用する必要があるのはなぜですか?
同期された意味:
Javaのすべてのオブジェクトはモニター(モニター)にすることができます。モニターは、ロック(ロック)、待機キュー(待機キュー)、エントリーキュー(エントリーキュー)で構成されます。
オブジェクトのメソッドの場合、同期されたキーワードがない場合、メソッドはいつでも任意の数のスレッドから呼び出すことができます。
同期キーワードが追加されたメソッドの場合、いつでもオブジェクトインスタンスのロックを取得した唯一のスレッドからのみ呼び出すことができます。
同期は、マルチスレッド同期操作を実装するために使用されます
同期の3つのアプリケーションメソッド、Javaの各オブジェクトは、同期を実現するための同期の基礎となるロックとして使用できます。
- 通常の同期方式(インスタンス方式)で、ロックは現在のインスタンスオブジェクトであり、同期コードを入力する前に現在のインスタンスのロックを取得する必要があります
- 静的同期メソッド。ロックは現在のクラスのクラスオブジェクトであり、同期コードを入力する前に現在のクラスオブジェクトのロックを取得する必要があります。
- 同期メソッドブロックでは、ロックは括弧内のオブジェクトであり、同期コードベースに入る前に、指定されたオブジェクトをロックし、指定されたオブジェクトのロックを取得します。
- 通常の同期メソッドの場合、ロックは現在のインスタンスオブジェクトです。
- 静的同期メソッドの場合、ロックは現在のクラスのClassオブジェクトです。
- 同期されたメソッドブロックの場合、ロックは同期されたブラケットで構成されたオブジェクトです。
wait()ロール
スレッドの同期には、wait()、notify()、notifyAll()、およびsynchonizedを一緒に使用する必要があります。
wait()は常にループで呼び出され、現在のスレッドを一時停止して、条件が真になるのを待ちます。Wait呼び出しは、別のスレッドがnotifyAll()を呼び出すまで待機してから戻ります。
当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁, 然后该线程会被添加到该对象的等待队列中(waiting queue), 只要该线程在等待队列中, 就会一直处于闲置状态, 不会被调度执行。 要注意wait()方法会强迫线程先进行释放锁操作,所以在调用wait()时, 该线程必须已经获得锁,否则会抛出异常。由于wait()在synchonized的方法内部被执行, 锁一定已经获得, 就不会抛出异常了。
notify()的功用
wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步。
当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waiting queue)的线程中取出任意一个线程, 将其添加到入口队列( entry queue) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。 如果等待队列中(waiting queue)没有线程, notify()方法不会产生任何作用。
notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting queue)中所有的线程都添加到入口队列中(entry queue)。
为什么wait() / notify() / notifyAll() 要放在synchronized的代码块中使用?
我们可以先来看看,不放在synchronized代码块中使用会有什么问题?
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
obj.notify();
}
复制代码
输出结果:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at xxxx.main(XXX.java:254)
复制代码
可以看到上面的代码输出结果,直接抛出异常了。下面就讲讲为何会出现这种问题。
wait() 和 notify()是用来实现多个线程之间的一个协调;wait() 表示让线程进入到阻塞状态,notify() 表示让阻塞的线程被唤醒, notifyAll() 则表述唤醒所有被阻塞的线程。 wait() 和 notify() 必然是成对出现的。 如果一个线程被wait()方法阻塞, 那么必然要另一个线程通过notify() 唤醒,从而实现多个线程之间的通信。
在多线程里面要实现多个线程之间的通信,除了管道以外,只能通过共享变量的方式来实现,也就是说第一个线程修改了公共共享变量,别的线程获得修改后的变量的值,从而完成数据的一个通讯;但是多线程的本身是具有并行执行的一个特性,也就是说,在同一个时间,多个线程是可以同时执行的,那么这种情况下,其它现在在访问共享变量之前,必须要知道,第一个线程已经修改了这个共享变量,否则就需要等待,或者是拿到原始数据,基于此基础上运输,产生数据不一致的场景;同时第一个线程在数据修改后,还需要把那个已经处于等待状态下的其它线程唤醒,所以在这种场景下,需要去实现线程之间的通信就必须要有一个静态条件,去控制多线程什么时候条件等待什么时候条件唤醒。而synchronized关键字就可以实现这样一个互斥条件,也就是在通过共享变量来实现多个线程通信的一个场景里面,参与通信的线程必须竞争到这共享变量的一个锁资源,才能够有资格对共享变量进行修改,那么修改完之后释放锁,其他线程就可以再次竞争同一个共享的锁,来获取修改之后的数据,从而完成线程之间的一个通信;所以这就是为什么wait() / notify() 必须需要在synchronized代码块中使用。
有了synchronized同步锁,就可以实现对于多个通信线程之间的一个互斥,从而实现条件等待和条件互斥的唤醒,另外为了避免wait() 和 notify()的错误使用,JDK强制的要求把wait() 和 notify() 写在了同步代码块中,否则就会抛出IllegalMonitorStateException。
/* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait(timeout, nanos);
* ... // Perform action appropriate to condition
* }
* </pre>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @param timeout the maximum time to wait in milliseconds.
* @param nanos additional time, in nanoseconds range
* 0-999999.
* @throws IllegalArgumentException if the value of timeout is
* negative or the value of nanos is
* not in the range 0-999999.
* /
复制代码