线程之间可以通过写作方式完成一个任务,这就需要线程之间互相通信。当多个线程操作同一共享资源的时候,需要彼此通信告知状态避免资源争夺。
线程通信方式有
- 共享内存:volatile 通过volatile让线程之间对共享资源的修改可见
- 消息传递:wait/notify/join 线程之间可以通过消息来进行彼此通信
- 管道流:管道输入/输出流的形式
主要了解消息传递,也就是wait/notify/join
wait/notify是一对都不陌生的线程通信方法,存在于Object类中
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
wait()、wait(long timeout, int nanos) 、notify()、notifyAll()本质都是调用了本地方法来完成的。
每个对象都有一个监视器锁,而平时说的获得锁就是获取到对象的监视器。通过对同一对象进行wait和notify本质上就是获取监视器锁后在释放并唤醒其他线程来抢占监视器锁。
wait和notify一定要在同步代码块中执行,因为线程之间的竞争是不确定性的,只有放在同步代码块中才能保证锁被获取到。wait一定要在notify之前执行,因为notify先于wait执行会导致notify唤醒失败。
wait执行后会立刻释放锁,以便其他线程可以执行notify,但是notify不会立刻释放锁,必须等到notify所在的线程执行完成才会释放锁。
调用wait方法后该线程就被放入等待队列中,notify执行之后会将等待队列中的线程放入同步队列中,进行锁的抢占。
join表示当前线程需要等待join的线程执行完成之后再继续执行。
从源码中可以看到,join方法内部实际上是调用了wait方法实现的等待。那么使用了wait之后并没有notify的动作,线程又如何重新执行的呢?
实际上线程再jvm层面实现run方法后会在jvm层面调用notify方法唤醒当前线程。