Java多线程学习(四)等待/通知(wait/notify)机制

转载请备注地址:https://juejin.im/post/5ab755fc6fb9a028c22aba1fjava

系列文章传送门:git

Java多线程学习(一)Java多线程入门程序员

Java多线程学习(二)synchronized关键字(1)github

Java多线程学习(二)synchronized关键字(2)面试

Java多线程学习(三)volatile关键字编程

Java多线程学习(四)等待/通知(wait/notify)机制微信

Java多线程学习(五)线程间通讯知识点补充多线程

系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。并发

本节思惟导图: ide

本节思惟导图

思惟导图源文件+思惟导图软件关注微信公众号:“Java面试通关手册” 回复关键字:“Java多线程” 免费领取。

一 等待/通知机制介绍

1.1 不使用等待/通知机制

当两个线程之间存在生产和消费者关系,也就是说第一个线程(生产者)作相应的操做而后第二个线程(消费者)感知到了变化又进行相应的操做。好比像下面的whie语句同样,假设这个value值就是第一个线程操做的结果,doSomething()是第二个线程要作的事,当知足条件value=desire后才执行doSomething()。

可是这里有个问题就是:第二个语句不停过经过轮询机制来检测判断条件是否成立。若是轮询时间的间隔过小会浪费CPU资源,轮询时间的间隔太大,就可能取不到本身想要的数据。因此这里就须要咱们今天讲到的等待/通知(wait/notify)机制来解决这两个矛盾

while(value=desire){
        doSomething();
    }
复制代码

1.2 什么是等待/通知机制?

通俗来说:

等待/通知机制在咱们生活中比比皆是,一个形象的例子就是厨师和服务员之间就存在等待/通知机制。

  1. 厨师作完一道菜的时间是不肯定的,因此菜到服务员手中的时间是不肯定的;
  2. 服务员就须要去“等待(wait)”;
  3. 厨师把菜作完以后,按一下铃,这里的按铃就是“通知(nofity)”;
  4. 服务员听到铃声以后就知道菜作好了,他能够去端菜了。

用专业术语讲:

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另外一个线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操做。上诉两个线程经过对象O来完成交互,而对象上的wait()方法notify()/notifyAll()方法的关系就如同开关信号同样,用来完成等待方和通知方之间的交互工做。

1.3 等待/通知机制的相关方法

方法名称 描述
notify() 随机唤醒等待队列中等待同一共享资源的 “一个线程”,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个线程”
notifyAll() 使全部正在等待队列中等待同一共享资源的 “所有线程” 退出等待队列,进入可运行状态。此时,优先级最高的那个线程最早执行,但也有多是随机执行,这取决于JVM虚拟机的实现
wait() 使调用该方法的线程释放共享资源锁,而后从运行状态退出,进入等待队列,直到被再次唤醒
wait(long) 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,若是没有通知就超时返回
wait(long,int) 对于超时时间更细力度的控制,能够达到纳秒

二 等待/通知机制的实现

2.1 个人第一个等待/通知机制程序

MyList.java

public class MyList {
	private static List<String> list = new ArrayList<String>();

	public static void add() {
		list.add("anyString");
	}

	public static int size() {
		return list.size();
	}

}
复制代码

ThreadA.java

public class ThreadA extends Thread {

	private Object lock;

	public ThreadA(Object lock) {
		super();
		this.lock = lock;
	}

	@Override
	public void run() {
		try {
			synchronized (lock) {
				if (MyList.size() != 5) {
					System.out.println("wait begin "
							+ System.currentTimeMillis());
					lock.wait();
					System.out.println("wait end "
							+ System.currentTimeMillis());
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
复制代码

ThreadB.java

public class ThreadB extends Thread {
	private Object lock;

	public ThreadB(Object lock) {
		super();
		this.lock = lock;
	}

	@Override
	public void run() {
		try {
			synchronized (lock) {
				for (int i = 0; i < 10; i++) {
					MyList.add();
					if (MyList.size() == 5) {
						lock.notify();
						System.out.println("已发出通知!");
					}
					System.out.println("添加了" + (i + 1) + "个元素!");
					Thread.sleep(1000);
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
复制代码

Run.java

public class Run {

	public static void main(String[] args) {

		try {
			Object lock = new Object();

			ThreadA a = new ThreadA(lock);
			a.start();

			Thread.sleep(50);

			ThreadB b = new ThreadB(lock);
			b.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}
复制代码

运行结果:

运行结果
从运行结果:"wait end 1521967322359"最后输出能够看出, notify()执行后并不会当即释放锁。下面咱们会补充介绍这个知识点。

synchronized关键字能够将任何一个Object对象做为同步对象来看待,而Java为每一个Object都实现了等待/通知(wait/notify)机制的相关方法,它们必须用在synchronized关键字同步的Object的临界区内。经过调用wait()方法可使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify()方法能够唤醒一个因调用wait操做而处于阻塞状态中的线程,使其进入就绪状态。被从新唤醒的线程会视图从新得到临界区的控制权也就是锁,并继续执行wait方法以后的代码。若是发出notify操做时没有处于阻塞状态中的线程,那么该命令会被忽略。

若是咱们这里不经过等待/通知(wait/notify)机制实现,而是使用以下的while循环实现的话,咱们上面也讲过会有很大的弊端。

while(MyList.size() == 5){
        doSomething();
    }
复制代码

2.2线程的基本状态

上面几章的学习中咱们已经掌握了与线程有关的大部分API,这些API能够改变线程对象的状态。以下图所示:

线程的基本状态切换图

  1. 新建(new):新建立了一个线程对象。

  2. 可运行(runnable):线程对象建立后,其余线程(好比main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。

  3. 运行(running):可运行状态(runnable)的线程得到了cpu时间片(timeslice),执行程序代码。

  4. 阻塞(block):阻塞状态是指线程由于某种缘由放弃了cpu使用权,也即让出了cpu timeslice,暂时中止运行。直到线程进入可运行(runnable)状态,才有 机会再次得到cpu timeslice转到运行(running)状态。阻塞的状况分三种:

    (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。

    (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

    (三). 其余阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程从新转入可运行(runnable)状态。

  5. 死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

备注: 能够用早起坐地铁来比喻这个过程:

还没起床:sleeping

起床收拾好了,随时能够坐地铁出发:Runnable

等地铁来:Waiting

地铁来了,但要排队上地铁:I/O阻塞

上了地铁,发现暂时没座位:synchronized阻塞

地铁上找到座位:Running

到达目的地:Dead

2.3 notify()锁不释放

当方法wait()被执行后,锁自动被释放,但执行玩notify()方法后,锁不会自动释放。必须执行完otify()方法所在的synchronized代码块后才释放。

下面咱们经过代码验证一下:

(完整代码:github.com/Snailclimb/…

带wait方法的synchronized代码块

synchronized (lock) {
				System.out.println("begin wait() ThreadName="
						+ Thread.currentThread().getName());
				lock.wait();
				System.out.println(" end wait() ThreadName="
						+ Thread.currentThread().getName());
			}
复制代码

带notify方法的synchronized代码块

synchronized (lock) {
				System.out.println("begin notify() ThreadName="
						+ Thread.currentThread().getName() + " time="
						+ System.currentTimeMillis());
				lock.notify();
				Thread.sleep(5000);
				System.out.println(" end notify() ThreadName="
						+ Thread.currentThread().getName() + " time="
						+ System.currentTimeMillis());
			}
复制代码

若是有三个同一个对象实例的线程a,b,c,a线程执行带wait方法的synchronized代码块而后bb线程执行带notify方法的synchronized代码块紧接着c执行带notify方法的synchronized代码块。

运行效果以下:

运行效果
这也验证了咱们刚开始的结论:必须执行完notify()方法所在的synchronized代码块后才释放。

2.4 当interrupt方法遇到wait方法

当线程呈wait状态时,对线程对象调用interrupt方法会出现InterrupedException异常。

Service.java

public class Service {
	public void testMethod(Object lock) {
		try {
			synchronized (lock) {
				System.out.println("begin wait()");
				lock.wait();
				System.out.println(" end wait()");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("出现异常了,由于呈wait状态的线程被interrupt了!");
		}
	}
}
复制代码

ThreadA.java

public class ThreadA extends Thread {

	private Object lock;

	public ThreadA(Object lock) {
		super();
		this.lock = lock;
	}

	@Override
	public void run() {
		Service service = new Service();
		service.testMethod(lock);
	}

}

复制代码

Test.java

public class Test {

	public static void main(String[] args) {

		try {
			Object lock = new Object();

			ThreadA a = new ThreadA(lock);
			a.start();

			Thread.sleep(5000);

			a.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}
复制代码

运行结果:

运行结果

参考:

《Java多线程编程核心技术》

《Java并发编程的艺术》

相关文章
相关标签/搜索