java 多线程之通信

共享堆内存变量属于通信的一种,今天要介绍的是使用Object类中的wait和notify方法进行通信,可以让两个线程共同地轮流做一件事

再看Object类:Object类是所有类的根父类,现在主要介绍wait方法和notify方法

void wait()让当前线程进入等待状态,直到别的线程将其唤醒(notify或者notifyAll方法)。

void notify()随机唤醒单个等待状态的线程,被唤醒的线程接着执行之前没完成的操作

void notifyAll()将所有等待状态的线程唤醒

我们接着做没做完的练习,让两个线程轮流打印0-100

package chen_chapter_9;

public class WaitNotifyTest01 {
	public static void main(String[] args) {
		Print p = new Print();
		Thread t1 = new Thread(p, "线程t1");
		Thread t2 = new Thread(p, "线程t2");
		t1.start();
		t2.start();

	}

}

class Print implements Runnable {
	static int i = 0;
	private boolean flag = true;

	public void run() {
		while (i < 100) {

			synchronized (this) {
				//if (i >= 100) {// 退出循环的判断条件
				//	break;
				//}
				if (!flag) { // flag为假就让线程等待
					try {
						this.flag = !flag;//将flag置反
						this.wait();    //这个线程进入等待状态,下次才能让另一个线程进入等待
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {
					
					System.out.println(Thread.currentThread().getName() + " : " + (i++));
					this.flag = !flag; //将flag置反,下次才能让另一个线程进入等待
					this.notify(); //唤醒在等待的线程
				}
			}

		}

	}
}
out:
如果没有else关键字,那么线程从等待中被唤醒后会继续执行进入等待前的操作,这样会多打印出一个数字100
分析:启动线程t1,t2,可能t1先进入同步代码块,锁住this对象,t2这时可能进入了run方法,当t1执行完打印
后设置flag=false,调用notify方法,但此时没有等待的线程,这句话不起作用,因为在while语句中,t1还满足
条件,没有退出循环体,也没有释放锁,再执行一次while循环体的语句,这时进入到if语句块,设置flag=true,
被设置为wait,释放锁,t2开始执行,打印语句,将flag=false,调用notify唤醒t1,但t2没释放锁,接着执行一
次while循环体,进入到if语句块,设置flag=true,被设置成等待,释放锁,此时又该t1执行了,打印语句,设置
flag=false,如此循环,如果打印语句不在else语句块中,当线程被唤醒后,会往下执行打印语句,导致结果不正确,
加入else语句块中后,保证每次while循环体内只有一个线程能执行

三个及多个线程之间的通信,notify方法只能唤醒一个正在等待的线程,如果多个线程在等待,再用这个方法,最后可能会出现所有线程都在等待的问题

notifyAll方法将所有在等待的线程唤醒,

注意:

1 synchronized同步代码块锁住的是哪个对象,就调用哪个锁对象的wait方法和notify,notifyAll方法,在这里锁住的是this,所以调用this.wait(),this.notify()

2 while和if 

if  wait()方法在if条件判断语句中,当被唤醒时,继续往下执行

while wait()方法在while循环语句中,当被唤醒时,要重新判断while条件

3匿名内部类和局部内部类在方法体中调用局部变量时(包括局部变量是对象的引用),要用final修饰,调用所在类的成员变量时,不需要final修饰,但是在main方法(静态方法)中调用类的所在类的成员变量时,需要用static修饰

先说下为什么调用局部变量时要用final修饰?

      方法中的类访问同一个方法中的局部变量,本来应该是天经地义的,但是这样会导致编译程序上实现的困难,因为内部类对象的生命期会超过局部变量的生命期

   1局部变量的生命期:当该方法被调用时,该方法中的局部变量在栈中被创建,方法调用结束时,退栈,局部变量消亡。

2 内部类生命期,跟其它类一样,当创建一个内部类对象后,引用为空时,这个内部类才消亡,不会因为它是在方法中定义的,当方法执行完毕它就消亡。

看到这里出现端倪了,当内部类调用所在方法的局部变量时,当所在的方法调用完成,局部变量就会消亡,但是内部类对象不一定消亡了,(只要它的引用不为空就不会消亡),这时再调用该内部类对象的方法或属性,如果用到该局部变量,程序就出问题了,内部类对象访问的属性不存在,所以用final关键字,实际是将局部变量复制一份(final的作用保证基本数据类型值不变,引用类型引用不变,尽量保证了局部变量和内部类的一致性),用做内部类的成员变量,当局部变量消亡时,还可以用该属性

还以上面为例,变成三个线程轮流打印0-99,将上面代码稍微修改下就可以了

public class WaitNotifyTest01 {
	public static void main(String[] args) {
		Print p = new Print();
		Thread t1 = new Thread(p, "线程t1");
		Thread t2 = new Thread(p, "线程t2");
		Thread t3 = new Thread(p, "线程t3");
		t1.start();
		t2.start();
		t3.start();

	}

}

class Print implements Runnable {
	static int i = 0;
	private boolean flag = true;

	public void run() {
		while (i < 100) {

			synchronized (this) {
				if (i >= 100) {// 退出循环的判断条件
					break;
				}
				if (!flag) { // flag为假就让线程等待
					try {
						this.flag = !flag;// 将flag置反
						this.wait(); // 这个线程进入等待状态,下次才能让另一个线程进入等待
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {

					System.out.println(Thread.currentThread().getName() + " : " + (i++));
					this.flag = !flag; // 将flag置反,下次才能让另一个线程进入等待
					this.notifyAll(); // 唤醒在等待的线程
				}
			}

		}

	}
}
out:并不是严格的轮流打印,但是每一轮三个线程各打一个

创建三个线程来演示多线程的通信,

package chen_chapter_9;

public class WaitNotifyAllTest01 {

	public static void main(String[] args) {
		final Print2 p2 = new Print2();// 匿名内部类与局部内部类访问成员变量和局部变量的不同
		Thread t1 = new Thread("线程t1") {
			@Override
			public void run() {
				while (true) {
					p2.print1();
					if (p2.stopNum == 100) {
						break;
					}
				}
			}
		};
		Thread t2 = new Thread("线程t2") {
			@Override
			public void run() {
				while (true) {
					p2.print2();
					if (p2.stopNum == 100) {
						break;
					}
				}
			}

		};
		Thread t3 = new Thread("线程t3") {
			@Override
			public void run() {
				while (true) {
					p2.print3();
					if (p2.stopNum == 100) {
						break;
					}
				}
			}
		};
		t1.start();
		t2.start();
		t3.start();

	}
}

class Print2 {
	private int i = 0;
	private int flag = 1;
	volatile int stopNum = 0;

	public void print1() {
		synchronized (this) {
			while (flag != 1) { // 设置条件,每次只让一个线程不等待,等它执行完后再唤醒其他等待的线程
				try {
					this.wait();// 捕获异常,往上抛出还要在后面捕获
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if (i < 100) {
				System.out.println(Thread.currentThread().getName() + " : " + i++);
			} else {
				this.stopNum = 100;
			}
			this.flag = 2;
			this.notifyAll();
		}

	}

	public void print2() {
		synchronized (this) {
			while (flag != 2) {
				try {
					this.wait();
				} catch (InterruptedException e) {

					e.printStackTrace();
				}
			}
			if (i < 100) {
				System.out.println(Thread.currentThread().getName() + " : " + i++);
			} else {
				this.stopNum = 100;
			}
			this.flag = 3;
			this.notifyAll();
		}

	}

	public void print3() {
		synchronized (this) {
			while (flag != 3) {
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if (i < 100) {
				System.out.println(Thread.currentThread().getName() + " : " + i++);
			} else {
				this.stopNum = 100;
			}
			this.flag = 1;
			this.notifyAll();
		}

	}
}
out:三个轮流打印,且顺序不变
分析:分析这块代码,设置flag=1,三个线程启动时,t1,t2,t3,不知道谁会先拿到执行权,这三个线程执行一遍
之后,只有t1能执行语句,while语句中t2,t3进入等待状态,当t1在同步代码块的内容执行完以后设置flag=2,
调用notifyAll方法,唤醒t2,t3,这时三个线程又开始抢cpu控制权,但因为flag=2,这时只有t2线程能执
行,while语句中将t1,t3设置成等待状态,t2执行完后将设置flag=3,调用notifyAll方法,将t1,t3唤醒,三
个线程又开始抢cpu控制权,但是因为flag=3,所以只能t3线程执行,while语句中让t1,t2变成等待状态,t3执
行完同步代码块内容后,设置flag=1,又回到刚开始的循环,在这里设置了个变量stopNum,是因为线程中的run
方法是死循环,要设置一个停止条件

sleep wait notify的区别

sleep:睡眠一段时间不释放锁,醒来后继续执行之前的任务,不释放锁

wait:等待后,释放对象锁,只能被别的线程唤醒notify,唤醒后接着执行之前未完的任务,注意在while和if语句块中的区别

notify不释放锁

猜你喜欢

转载自blog.csdn.net/sinat_41132860/article/details/84545282