Java线程间协作(一)

Posted by kyle on April 28, 2018

等待与通知:wait/notify

● 一个线程因其执行目标动作所需的保护条件未满足而被暂停的过程就被称为等待(wait)

● 一个线程更新了系统的状态,使得其他线程所需的保护条件得以满足的时候唤醒那些被暂停的线程的过程就被称为通知(notify)

使用Object.wait()实现等待:
synchronized(someObject) {
	while(保护条件不成立) {
		// 暂停当前线程
		someObject.wait();
	}

	// 执行目标动作
	doAction();
}

使用时的注意事项: 1、一个线程只有在持有一个对象的内部锁的情况下才能够调用该对象的wait方法,否则按照wait方法的实现会抛出IllegalMonitorStateException异常,因此wait的调用总是放在相应对象所引导的临界区之中

2、考虑到等待线程在其被唤醒、继续运行到其再次持有相应对象的内部锁的这段时间内,由于其他线程可能抢先获得相应的内部锁并更新了相关共享变量而导致该线程所需的保护条件又再次不成立,因此Object.wait()调用返回之后我们需要再次判断此时保护条件是否成立,故而此处保护条件的判断放在while循环的判断语句中。

使用Object.notify()实现通知:
synchronized(someObject) {
	// 更新等待线程的保护条件涉及的共享变量
	updateSharedState();
	// 唤醒其他线程
	someObject.notify();
}

通知方法包含两个要素:更新共享变量唤醒其他线程。 与wait方法一样,notify的调用必须放在相应对象内部锁所引导的临界区之中

wait与notify之间的协作

假设someObject为Java中任意一个类的实例。someObject.wait()会以原子操作的方式使其执行线程暂停,并使该线程释放其持有的对应的内部锁

someObject.notify()可以唤醒someObject上的一个任意的等待线程。被唤醒的等待线程在其占用处理器继续运行的时候,需要再次申请someObject对应的内部锁。被唤醒的线程在其再次持有someObject对应的内部锁的情况下继续执行someObject.wait()中剩余的指令,直到wait方法返回。

someObject.notify()的执行线程持有的相应对象的内部锁只有在notify调用所在的临界区代码执行结束后才会被释放,notify本身并不会将这个内部锁释放。因此,为了使等待线程在其被唤醒之后能尽快再次获得相应的内部锁,我们要尽可能地将someObject.notify()调用放在靠近临界区结束的地方

notifyAll()

Object.notify()所唤醒的仅是相应对象上的一个任意等待线程,所以这个唤醒的线程可能不是我们真正想要唤醒的那个线程。所以可以用Object.notifyAll()唤醒相应对象上的所有等待线程。

Thread.join()

Thread.join()可以使当前线程等待目标线程结束之后才继续运行。

Thread.join()使用范例:
public static void main(String[] args)
 throws InterruptedException {
	Thread t = new Thread(() -> {
		// 实现run方法
	});
	t.start();
    t.join();
}

join()必须放在start()之后执行,因为join()的实现原理实际是:检测到目标线程未处于alive状态的时候会调用wait方法来暂停当前线程,直到目标线程终止。

Java条件变量

java.util.concurent.looks.Condition接口可以作为wait/notify的替代品来实现等待/通知,它为解决过早唤醒问题提供了支持,并解决了Object.wait(long)不能区分其返回是否是由等待超时而导致的问题。

过早唤醒:当存在多个线程且这些线程的保护条件并不一致时,由于线程被唤醒具有随机性,这就会导致某些保护条件尚未成立的线程被唤醒,继而由于保护条件尚未成立该线程再次进入等待状态,白白浪费了上下文切换的开销。

Condition接口定义的await、signal和signalAll分别相当于wait、notify和notifyAll。

Condition实例必须通过显式锁实例的newCondition()方法来创建。

wait/notify要求其执行线程持有这些方法所属对象的内部锁,类似地,Condition的await/signal也要求其执行线程持有该Condition实例的显式锁。

Condition使用范例:
class ConditionUsage {
	private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void aGuaredMethod()
     throws InterruptedException {
        lock.lock();
        try {
            while (保护条件) {
                condition.await();
            }

            // 执行目标动作
            // xxx
        } finally {
            lock.unlock();
        }
    }

    public void anNotificationMethod()
     throws InterruptedException {
        lock.lock();
        try {
            // 更新共享变量
            // xxx

            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}
Condition解决过早唤醒的问题

同步对象内部维护多个Condition实例:cond1、cond2、……,用以区分保护条件不同的线程同步控制。

Condition解决Object.wait(long)是否等待超时的问题

Object.wait(long)既无返回值也不会抛出异常,所以我们无法区分其返回是由其他线程通知了还是由于等待超时。

Condition.awaitUntil(Date deadline)可以解决这一问题。其唯一参数deadline表示等待的最后期限,过了这个时间点就算等待超时。当它返回值为true时,就表示进行的等待尚未达到最后期限,即此线程是被其他线程执行了signal/signalAll唤醒的。如果它调用返回false,就表示是由等待超时引起的停止等待。