java多线程基本概述(五)——线程通信

时间:2022-11-02 15:01:26

线程之间的通信可以通过共享内存变量的方式进行相互通信,也可以使用api提供的wait(),notify()实现线程之间的通信。wait()方法是Object类的方法,改方法用来将当前的线程置入"预执行队列"中,并且在wait()方法代码处停止执行进行等待,知道接收到同一个monitor对象的notify()或者notifyAll()方法的通知或者是收到中断。在调用wait()方法之前,线程必须获得锁,即只能在同步方法或者同步块中调用wait()方法,在执行wait()后,当前线程释放锁。在wait()方法释放锁后,其他处于等待该监视器对象的锁的线程将相互竞争获得此监视器对象锁。如果没有同步块或者同步方法,那么调用该方法将会抛IllegalMonitorStateException.并且由于可能发生中断或者是虚假唤醒,那么最好将wait()方法放在循环中调用。下面是api文档:

public final void wait()
throws InterruptedException
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor.
The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor
to wake up either through a call to the notify method or the notifyAll method. The thread then waits
until it can re-obtain ownership of the monitor and resumes execution. As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop: synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
} This method should only be called by a thread that is the owner of this object's monitor.
See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification.
              The interrupted status of the current thread is cleared when this exception is thrown.
See Also:
notify(), notifyAll()

下面是wait(long timeout)的api:

public final void wait(long timeout)
throws InterruptedException
Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
The current thread must own this object's monitor. This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Thread T becomes disabled for thread scheduling purposes and lies dormant until one of four things happens: Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.
Some other thread invokes the notifyAll method for this object.
Some other thread interrupts thread T.
The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.
The thread T is then removed from the wait set for this object and re-enabled for thread scheduling. It then competes in the usual manner with other threads for the right to synchronize on the object; once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked. Thread T then returns from the invocation of the wait method. Thus, on return from the wait method, the synchronization state of the object and of thread T is exactly as it was when the wait method was invoked.
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one: synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
} (For more information on this topic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
If the current thread is interrupted by any thread before or while it is waiting, then an InterruptedException is thrown. This exception is not thrown until the lock status of this object has been restored as described above. Note that the wait method, as it places the current thread into the wait set for this object, unlocks only this object; any other objects on which the current thread may be synchronized remain locked while the thread waits. This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor. Parameters:
timeout - the maximum time to wait in milliseconds.
Throws:
IllegalArgumentException - if the value of timeout is negative.
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
See Also:
notify(), notifyAll()

方法notify()也要在同步方法或同步块中使用,该方法用来通知那些处于wait()方法的线程,并且这notify()和wait()方法的监视器对象应该是同一个,才可以进行唤醒。如果有多个线程处于wait()的等待中,那么也只能抽取一个进行唤醒,使wait()方法获取锁对象和继续执行。需要注意的是:在执行完notify()方法后,当前线程不会马上释放对象锁,wait()的线程也不会马上拥有锁。要等到notify()方法所在的线程将程序执行完成后,也就是退出同步快或者同步方法后才释放锁,继而wait()才获得对象锁。

也就是说:notify()操作可以唤醒一个因为调用了wait()方法而处于阻塞状态的线程,使其处于就绪状态。

下面是notify()api:

public final void notify()
Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object. This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways: By executing a synchronized instance method of that object.
By executing the body of a synchronized statement that synchronizes on the object.
For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object's monitor. Throws:
IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.
See Also:
notifyAll(), wait()

例子:

package soarhu;
class Service{ private Object lock; public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
System.out.println("waiting enter..."+System.currentTimeMillis());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting outer...."+System.currentTimeMillis());
}
} void notifying(){
synchronized (lock){
System.out.println("notifying enter..."+System.currentTimeMillis());
try {
Thread.sleep(3000);
lock.notify();
System.out.println("notifying outer..."+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
Thread t1 = new Thread(){
@Override
public void run() {
service.waiting();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
service.notifying();
}
}; t1.start();
Thread.sleep(1000);
t2.start();
}
}

输出结果:

waiting enter...1492483779692
notifying enter...1492483780692
notifying outer...1492483783699
waiting outer....1492483783699

如果先启动t2线程那么就可能造成死锁。或者不能确保t1线程先启动的话,死锁必然发生。

下面修改notify()方法,验证notify()必须执行完后才释放锁

   void notifying(){
synchronized (lock){
System.out.println("notifying enter..."+System.currentTimeMillis());
try {
Thread.sleep(3000);
lock.notify();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("notifying outer..."+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出结果:

waiting enter...1492484010466
notifying enter...1492484011466
0
1
2
3
4
5
6
7
8
9
notifying outer...1492484014466
waiting outer....1492484014466

可以看到,即使notify()在循环前调用,但仍要使所在方法执行完成之后才释放监视器锁。

那么如何避免可能发生的死锁呢?修改代码如下:

package soarhu;
class Service{ private Object lock;
private boolean waitFirst = true; public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
while (waitFirst){
System.out.println("waiting enter..."+System.currentTimeMillis());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting outer...."+System.currentTimeMillis());
}
}
} void notifying(){
synchronized (lock){
System.out.println("notifying enter..."+System.currentTimeMillis());
try {
Thread.sleep(3000);
lock.notify();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("notifying outer..."+System.currentTimeMillis());
waitFirst=false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
Thread t1 = new Thread(){
@Override
public void run() {
service.waiting();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
service.notifying();
}
}; t2.start();
Thread.sleep(1000);
t1.start();
}
}

输出结果:

notifying enter...1492486139592
0
1
2
3
4
5
6
7
8
9
notifying outer...1492486142592

可以看到waitting()方法没有被执行了,从而避免了可能发生死锁问题,但是虽然避免了死锁那么wait/antify的意义也就不存在了。

notify()与notifyAll()区别

wait()表示:          放弃当前对资源的占有权,等啊等啊,一直等到有人通知我,我才会运行后面的代码。 
notify()表示:        当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复, 然后继续运行wait()后面的语句; 
notifyAll()表示:     当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。

例子:

package other;
class Service{
private Object lock; public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
System.out.println("waiting enter...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting outer....");
}
} void notifying(){
synchronized (lock){
System.out.println("notifying enter...");
try {
Thread.sleep(1000);
lock.notify();
System.out.println("notifying outer...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
service.waiting();
}
}.start();
}
Thread.sleep(1000);
Thread t2 = new Thread(){
@Override
public void run() {
service.notifying();
}
};
t2.start();
}
}

输出结果:产生死锁。有一个没出来。

waiting enter...
waiting enter...
notifying enter...
notifying outer...
waiting outer....

改为notifyAll()后:

void notifying(){
synchronized (lock){
System.out.println("notifying enter...");
try {
Thread.sleep(1000);
lock.notifyAll();
System.out.println("notifying outer...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出结果:

waiting enter...
waiting enter...
notifying enter...
notifying outer...
waiting outer....

全部退出wait()方法。

notifyAll()/wait()中。if while的事情。

package soarhu;

import java.util.ArrayList;
import java.util.List; class Service{ private Object lock;
private List<String> list = new ArrayList<>(); public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
try {
if(list.size()==0){ //此处错误的用法,应该用while
System.out.println("wait enter..."+Thread.currentThread().getName());
lock.wait();
System.out.println("wait outer..."+Thread.currentThread().getName());
}
list.remove(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size: "+list.size());
}
} void notifying(){
synchronized (lock){
System.out.println("ADD enter..."+Thread.currentThread().getName());
list.add("hello");
lock.notifyAll();
System.out.println("ADD outer..."+Thread.currentThread().getName());
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
service.waiting();
}
}.start();
}
Thread.sleep(1000);
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
service.notifying();
}
}.start();
}
}
}

输出结果:抛出异常。。。

wait enter...Thread-0  //thread-0 等待
wait enter...Thread-1           //thread-1 等待
ADD enter...Thread-2  
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.remove(ArrayList.java:492)
ADD outer...Thread-2
wait outer...Thread-1           //thread-1 退出 
size: 0
wait outer...Thread-0 //thread-0 退出,再此过程中抛出异常
at soarhu.Service.waiting(Test.java:23)
ADD enter...Thread-3
ADD outer...Thread-3
at soarhu.Test$1.run(Test.java:50) Process finished with exit code 0

分析结果:

开始线程0和线程2进入,判断得知list的长度为0则进行等待。然后线程2开始执行添加一个元素,并唤醒线程0和线程1.唤醒后,线程1执行remove()使list.size()为0.然后线程1从wait()方法处继续执行,再次执行remove.而此时size已经为0.所以会抛异常。而如果将if改为while,那么结果将正确,因为这样每次wait()被唤醒后每次都会检测size的大小。如果已经被别的线程修改过了,那么将继续wait().

 void waiting(){
synchronized (lock){
try {
while(list.size()==0){ //ok
System.out.println("wait enter..."+Thread.currentThread().getName());
lock.wait();
System.out.println("wait outer..."+Thread.currentThread().getName());
}
list.remove(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size: "+list.size());
}
}

输出结果:

wait enter...Thread-0 //thread-0 等待
wait enter...Thread-1          //thread-1 等待
ADD enter...Thread-2            
ADD outer...Thread-2
wait outer...Thread-1          //thread-1 退出
size: 0
wait outer...Thread-0 //thread-0 退出
wait enter...Thread-0 //thread-0 继续等待
ADD enter...Thread-3
ADD outer...Thread-3
wait outer...Thread-0 //thread-0 退出
size: 0 Process finished with exit code 0

分析结果可知,

开始线程0和线程2进入,判断得知list的长度为0则进行等待。然后线程2开始执行添加一个元素,并唤醒线程0和线程1.唤醒后,线程1执行remove()使list.size()为0.然后线程1从wait()方法处继续执行,

此时再次进行size()判断,得知为0,继续等待。线程3进行加1,线程0现在才有机会退出等待。如果add的线程数少,那么又会发生死锁。改写代码:

public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
for (int i = 0; i < 2; i++) { //这里的数量为2
new Thread(){
@Override
public void run() {
service.waiting();
}
}.start();
}
Thread.sleep(1000);
for (int i = 0; i < 1; i++) { //此时数量改为1
new Thread(){
@Override
public void run() {
service.notifying();
}
}.start();
}
}
}

输出结果:死锁

wait enter...Thread-0
wait enter...Thread-1
ADD enter...Thread-2
ADD outer...Thread-2
wait outer...Thread-1
size: 0
wait outer...Thread-0
wait enter...Thread-0

分析:可以对比上次的执行结果。线程0再次进入等待后,此时没有新的线程来通知他进行唤醒,即使唤醒了,那么如果list中没有元素依然要进行等待。