深入理解线程之间的通信

全文概要

本文主要介绍wait()/wait(long timeOut)/notify()/notifyAll()的基本使用情况,包括以下内容:
  • wait()和notify()联合使用,完成线程之间的通信交互;
  • 通过一个案例说明wait(long timeOut)的用法;
  • 通过一个案例说明notifyAll()的用法;
  • 总结下notify/wait使用场景

wait()/notify()使用案例

 本案例是通过使用wait()和notify()完成两个线程之间的顺序输出。线程1先循环5次,接着线程2循环5次,接着线程1再循环5次,最后线程2循环5次。
  • 代码案例
package com.tml.javaCore.thread;

/**
 * <p>
 * 介绍两个线程之间的通讯
 * wait()/notify()
 * 
 * @author Administrator
 *
 */
public class ThreadCommunication {
	public static void main(String[] args) {
		ThreadCommunication communication = new ThreadCommunication();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					communication.print();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}
		}, "thread_01");

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					communication.print();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}
		}, "thread_02");

		t1.start();
		t2.start();
	}

	private void print() throws InterruptedException {
		synchronized (this) {
			for (int i = 0; i < 10; i++) {
				this.notify();//唤醒被当前对象加锁的线程,释放对象锁,但是不会让出CPU的执行权
				if (i % 5 == 0) {
					this.wait();//让当前已经获取对象锁的线程阻塞,同时释放掉对象锁和让出CPU的执行权
				}
				Thread.sleep(500);
				System.out.println(Thread.currentThread().getName() + " is printing: " + i);
			}
		}
	}

}
  • 输出结果
 thread_01 is printing: 0
 thread_01 is printing: 1
 thread_01 is printing: 2
 thread_01 is printing: 3
 thread_01 is printing: 4
 thread_02 is printing: 0
 thread_02 is printing: 1
 thread_02 is printing: 2
 thread_02 is printing: 3
 thread_02 is printing: 4
 thread_01 is printing: 5
 thread_01 is printing: 6
 thread_01 is printing: 7
 thread_01 is printing: 8
 thread_01 is printing: 9
 thread_02 is printing: 5
 thread_02 is printing: 6
 thread_02 is printing: 7
 thread_02 is printing: 8
 thread_02 is printing: 9

  • 结果说明
  1. 线程1首先获取对象锁,拥有CPU的执行权,当执行到this.notify()时,线程1会唤醒在communication对象上休眠的线程2,让其进入就绪状态;
  2. 换句话说线程1会释放communication的对象锁,但是线程1不会放弃CPU的执行权,只有当synchronized中的代码执行完毕后或者由于其他原因导致线程阻塞才会释放CPU的执行权;
  3. 当线程1循环到第五次的时候,会执行this.wait(),此时线程1会释放对象锁,同时让出CPU的执行权限,也就是说线程1进入阻塞状态,只有当调用communication对象的notify()/notifyAll()方法才会让线程1进入就绪状态;
  4. 由于线程2此时是就绪状态,立马获取空闲的CPU资源,当线程2执行到this.notify()时,线程2会唤醒在communication对象上休眠的线程1,让其进入就绪状态,后续逻辑以此类推,直至线程的任务完成。

wait(long timeOut)使用案例

  • 代码案例
package com.tml.javaCore.thread;
/**
 * <p>线程之间的通信
 * wait(long timeOut)
 * @author Administrator
 *
 */
public class WaitDemo {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new  Runnable() {
			public void run() {
				synchronized (this) {
					System.out.println(Thread.currentThread().getName() + " is executing!");
					//模拟死循环,t1线程不释放对象锁
					while(true){
						
					}
				}
				
			}
		}, "thread_01");
		t1.start();
		System.out.println(Thread.currentThread().getName() + " start!");
		synchronized (t1) {
			System.out.println(Thread.currentThread().getName() + " wait!");
			t1.wait(3000);//由于t1线程死循环,无法释放对象锁,当前主线程阻塞,约3秒后,t1线程释放对象锁,唤醒主线程
		}
		System.out.println(Thread.currentThread().getName() + " end!");
		
		
		
		
	}
	
	

}
  • 输出结果
 main start!
 thread_01 is executing!
 main wait!
 main end!

  • 结果说明
  1. 主线程获取CPU的执行权,调用t1.start()时,让t1线程进入就绪状态;
  2. 当主线程获取对象锁t1时,会调用t1.wait(3000),同时释放对象锁,线程1获取空闲的CPU资源后,开始执行run()方法,但是run()方法中有一个死循环,也就是线程1不会释放对象锁,同时由于同步代码块中的逻辑没有完成,因此线程1也不会释放CPU的执行权,这就导致了主线程无法执行后续代码;
  3. 当约3秒后,线程1释放对象锁,主线程获取CPU资源,主线程的逻辑完成;
  4. 调用t1.wait(3000)为啥是让主线程等待?原因就是所谓的等待是让正在执行任务的线程等待,因为主线程调用t1.wait(3000),因此正在执行的线程是主线程。

notifyAll()使用案例

  • 代码案例
package com.tml.javaCore.thread;
/**
 * <p>线程之间的通信
 * wait()/notifyAll()
 * @author Administrator
 *
 */
public class NotifyAllDemo {
	//锁对象
	private static Object object = new  Object();
	
	public static void main(String[] args) {
		NotifyAllDemo demo = new  NotifyAllDemo();
		
		//使用匿名内部类启动线程1
		new Thread(new Runnable() {
			@Override
			public void run() {
				demo.threadPrint();
			}
		}, "thread_01").start();;
		
		//使用匿名内部类启动线程2
		new Thread(new Runnable() {
			@Override
			public void run() {
				demo.threadPrint();
			}
		}, "thread_02").start();
		
		//使用匿名内部类启动线程3
		new Thread(new Runnable() {
			@Override
			public void run() {
				demo.threadPrint();
			}
		}, "thread_03").start();
		
		
		try {
			//主线程休眠3秒
			Thread.sleep(3000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		synchronized (object) {
			System.out.println(Thread.currentThread().getName() + " :is ready to notifyAll!");
			//唤醒object上休眠的三个线程
			object.notifyAll();
			System.out.println(Thread.currentThread().getName() + " :is finished!");
		}
		
		
		
	}
	
	
	private  void threadPrint(){
		synchronized (object) {
			try {
				System.out.println(Thread.currentThread().getName() + " :is ready to wait!");
				object.wait();//当前线程进入阻塞状态,同时释放对象锁
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " :is finished!");
		}
	}
	
	

}
  • 输出结果
 thread_01 :is ready to wait!
 thread_02 :is ready to wait!
 thread_03 :is ready to wait!
 main :is ready to notifyAll!
 main :is finished!
 thread_03 :is finished!
 thread_02 :is finished!
 thread_01 :is finished!

  • 结果说明
  1. 主线程中使用匿名内部类的方式启动三个线程,紧接着主线程休眠3秒;
  2. 三个线程由于使用了相同的对象锁,随机一个线程获取对象锁,当该线程执行到object.wait()时,会释放对象锁,紧接着剩下两个线程以此类推;
  3. 当三个线程都进入阻塞状态时,主线程在休眠三秒后重新拥有CPU的执行权,当主线程获取对象锁执行到object.notifyAll()时,会将object对象上休眠的三个线程都唤醒;
  4. 三个线程唤醒后,依次获取到对象锁,完成各自的任务。

总结

  • wait()/notify()/notifyAll()等方法,必须写在synchronized代码块或者synchronized方法中,否则会抛出java.lang.IllegalMonitorStateException运行时异常;
  • wait()/notify()/notifyAll()等方法为啥都是定义在Object类中而不是定义在Thread类中?因为这些方法都是依赖对象同步锁,而java中所有的对象都可以作为同步锁,因此这些方法必须定义在超类Object中,同时这些方法为final的,不可以被重写。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值