本文讲解java线程间的通信,通过wait(),notify(),notifyAll().来实现。程序通过生产者Producer和消费者Consumer模式的例子来展开。

java 两个线程通信 java线程之间的通信_System


本文通过对程序示例的创建和改进过程,实现对以下三点的理解:

1. 实现线程同步

      【有一个缓冲区,存放着一种记录结构 [name , sex] ,  生产者不停地向缓冲区生产 张三 男 , 李四 女 ; 消费者不停地 消费 张三 男 , 李四 女,  若生产者向缓冲区 刚产生完 张三 , 还没生产 男 时线程被切换到消费者,那么消费者从缓冲区取出的 就是 张三 女 , 就发生线程不同步了 】

2. 实现线程之间的通信

      要实现的是:生产者每生产一个 对象(张三  男)或者(李四 女) 后,就轮到消费者 消费一个对象(张三  男)或者(李四 女) ,消费者消费完一个对象后,再由生产者生产一个,再消费一个,如此循环。 即要设置不同线程调用run()方法的先后顺序,就要实现线程之间的通信。用wait(),notify(),notifyAll()方法。

3. 对wait(),notify(),notifyAll()主语的深层次理解。

java 两个线程通信 java线程之间的通信_wait_02

注意: wait(), notify()的主语是synchronized(o) 中的监视器对象o .  程序一般不写主语,表示是this.wait(),this.notify(),表示监视器对象是当前的类对象。如果synchronized(o) 中的 监视器对象o不是 this , 则一定要写o.wait(),o.notify().

---------------------------------------------------------------------------------------------------------------------

1. TestThread14.java

package com.thread;

public class TestThread14 {

	public static void main(String[] args) {
		Q q = new Q();
		Producer producer = new Producer(q);
		Consumer consumer = new Consumer(q);
		new Thread(producer).start();
		new Thread(consumer).start();

	}

}

class Producer implements Runnable {
	Q q;

	public Producer(Q q) {
		this.q = q;
	}

	public void run() {
		int flag = 0;

		while (true) {

			if (flag == 1) {
				q.name = "张三  ";
				// 加上这句使得 线程不同步的情况更容易出现
				try {
					Thread.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
				q.sex = "男";

			} else {
				q.name = "李四";
				q.sex = "女";
			}

			flag = (flag + 1) % 2;
		}

	}
}

class Consumer implements Runnable {

	Q q;

	public Consumer(Q q) {
		this.q = q;
	}

	public void run() {

		while (true) {

			System.out.print(q.name);
			System.out.println(":" + q.sex);

		}
	}

}

class Q {
	String name = "unknown";
	String sex = "unknown";
}

运行结果:

java 两个线程通信 java线程之间的通信_notify_03

张三 变成 女,标明线程不同步。解决办法,在两个类中要同步的代码块,加上同一个监视器对象synchronized(q) .见下面程序2.

2. TestThread14

package com.thread;

public class TestThread14 {

	public static void main(String[] args) {
		Q q = new Q();
		Producer producer = new Producer(q);
		Consumer consumer = new Consumer(q);
		new Thread(producer).start();
		new Thread(consumer).start();

	}

}

class Producer implements Runnable {
	Q q;

	public Producer(Q q) {
		this.q = q;
	}

	public void run() {
		int flag = 0;

		while (true) {
			synchronized (q) {
				if (flag == 1) {
					q.name = "张三  ";
					// 加上这句使得 线程不同步的情况更容易出现
					try {
						Thread.sleep(1);
					} catch (Exception e) {
						e.printStackTrace();
					}
					q.sex = "男";

				} else {
					q.name = "李四";
					q.sex = "女";
				}
			}
			

			flag = (flag + 1) % 2;
		}

	}
}

class Consumer implements Runnable {

	Q q;

	public Consumer(Q q) {
		this.q = q;
	}

	public void run() {

		while (true) {
			
			synchronized (q) {
				System.out.print(q.name);
				System.out.println(":" + q.sex);
			}
			

		}
	}

}

class Q {
	String name = "unknown";
	String sex = "unknown";
}

运行结果: 张三 男,李四 女 结果正确。 name 和 sex 已经实现同步。

java 两个线程通信 java线程之间的通信_java 两个线程通信_04


注意:此处很特殊,是要将两个类中的代码块同步。但是方法是一样的,即:给两个类中要同步的代码块加上同一个监视器对象。本例中,类Producer和类Consumer共同拥有对象Q q, 所以,用q 作为他们共有的监视器,确保两个类中需要同步的代码块同步。

以上已经实现线程同步,但如何实现线程间的通信?即如何实现 生产者 生产一个 张三 男,消费者 打印一个 张三男,然后生产者再生产一个,消费者打印一个,依次执行。这就需要两个线程之间的通信。


3. TestThread14.java

package com.thread;

public class TestThread14 {

	public static void main(String[] args) {
		Q q = new Q();
		Producer producer = new Producer(q);
		Consumer consumer = new Consumer(q);
		new Thread(producer).start();
		new Thread(consumer).start();

	}

}

class Producer implements Runnable {
	Q q;

	public Producer(Q q) {
		this.q = q;
	}

	public void run() {
		int flag = 0;

		while (true) {
			synchronized (q) {
				
				if (q.bFull) {
					// 如果 缓冲区q有数据,则 生产者暂停生产
					try{q.wait();}catch(Exception e){e.printStackTrace();}					
				}
				
				
				if (flag == 1) {
					q.name = "张三  ";					
					try {Thread.sleep(1);} catch (Exception e) {e.printStackTrace();}					
					q.sex = "男";

				} else {
					q.name = "李四";
					q.sex = "女";
				}
				
				//生产完毕,缓冲区标识有数据
				q.bFull = true;
				// 生产完毕后,缓冲区有数据,释放监视器q,通知等待监视器q的线程(消费者)
				q.notify();
			}
			

			flag = (flag + 1) % 2;
		}

	}
}

class Consumer implements Runnable {

	Q q;

	public Consumer(Q q) {
		this.q = q;
	}

	public void run() {

		while (true) {
			
			synchronized (q) {
				if (!q.bFull) {
					//若缓冲区是空的,则消费者暂停读取数据。
					try{q.wait();}catch(Exception e){e.printStackTrace();}
				}
				
				System.out.print(q.name);
				System.out.println(":" + q.sex);
				// 消费者读取完数据后,标识缓冲区为空
				q.bFull = false;
				// 缓冲区为空,消费者释放监视器q,通知等待监视器q的线程(生产者)
				q.notify();
			}
			

		}
	}

}

class Q {
	String name = "unknown";
	String sex = "unknown";
	boolean bFull = false;
}



运行结果:正确,生产者生产一条记录,消费者消费一条记录,再生产一条,再消费一条。

java 两个线程通信 java线程之间的通信_java 两个线程通信_05

注意: 线程通信应使用q.wait(),q.notify().  要使用共同的监视器对象q。

----------------------------------------------------------------------------------------------------------------

上面的代码设计很乱,缓冲区Q的数据是通过Q外的类进行操作的,很危险,且代码冗杂。更好的设计是:把生产者生产数据及消费者消费数据封装到缓冲区Q中。

push(String name , String sex) ; get() 方法。

注意新的封装仍然要实现两点:

1). 线程同步  (不能取到 数据 张三 女 , 或者 李四 男)

2). 线程通信 (生产1条记录,消费1条记录,依次循环,不能乱。比如生产了3条记录,消费1条记录等等。)


4.TestThread14

package com.thread;


public class TestThread14 {

	public static void main(String[] args) {
		Q q = new Q();
		Producer producer = new Producer(q);
		Consumer consumer = new Consumer(q);
		new Thread(producer).start();
		new Thread(consumer).start();

	}

}

class Producer implements Runnable {
	Q q;

	public Producer(Q q) {
		this.q = q;
	}

	public void run() {
		int flag = 0;

		while (true) {
			if (flag == 0) {
				q.push("张三", "男");
			}else {
				q.push("李四", "女");
			}
			flag = (flag+1)%2;
		}

	}
}

class Consumer implements Runnable {

	Q q;

	public Consumer(Q q) {
		this.q = q;
	}

	public void run() {

		while (true) {			
			q.get();
		}
	}

}

class Q {
	String name = "unknown";
	String sex = "unknown";
	boolean bFull = false;
	
	public synchronized void push(String name , String sex){
		if (bFull) {
			//若缓冲区是满的,则生产者暂停生产。等待 监视器this.
			try{wait();}catch(Exception e){e.printStackTrace();}
		}
		
		this.name = name;
		try{Thread.sleep(1);}catch(Exception e){e.printStackTrace();}
		this.sex = sex;
		// 生产完毕后,标识缓冲区为满
		bFull = true;
		// 生产完成后,释放 监视器this的锁旗标,通知等待this锁旗标的其他线程
		notify();
	}
	
	public synchronized void get(){
		if (!bFull) {
			// 若缓冲区为空,则消费者暂时不读取,等待 监视器this的锁旗标
			try{wait();}catch(Exception e){e.printStackTrace();}
		}
		System.out.print(name);
		System.out.println(":" + sex);
		//消费完毕后,缓冲区为空
		bFull = false;
		//消费完毕后,缓冲区为空,消费者释放监视器this的所旗标,通知等待监视器this锁旗标的其他线程
		notify();
		
	}
}

程序结果:

java 两个线程通信 java线程之间的通信_notify_06

注意: 对比 程序4 和 程序3 ,功能一样,设计不一样。

最重要的区别:实现同步的监视器对象不同(程序4的监视器是q对象,而程序5的监视器是this),使得程序4 逻辑更清楚,代码更简洁。

总结:一个类A的属性尽量在A类体内操作,尽量不要让其他类B直接操作类A的属性,因为这样一方面很危险,第二可读性差,第三代码冗杂。即要坚持面向对象的思想。