概述
在之前的程序中,线程通过同步来正确地访问共享资源。这些线程之间是相互独立的,并不存在依赖关系。它们各自竞争CPU资源,互不相让,并且无条件地阻止其他线程对共享资源的异步访问。然而,有很多现实问题要求不仅要同步地访问同一共享资源,而且线程之间还彼此相互牵制,相互通信。
实例
生产者和消费者问题,生产者不断生产,消费者不断取走生产者生产的产品。生产者生产出信息之后将其放到一个区域之中,之后消费者从此区域里取出数据。
工具
java中提供了如下三个方法实现线程之间的通信。
1、wait()方法:调用wait()方法会挂起当前线程,并释放共享资源的锁。
2、notify()方法:调用任意对象的notify()方法会在因调用该对象的wait()方法而阻塞的线程中随机选择一个线程解除阻塞,但要等到获得锁后才可真正执行。
3、notifyAll()方法:调用了notifyAll()方法会将因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。
wait()、notify()和notifyAll()这三个方法都是Object类中的final方法,被所有的类继承且不允许重写。这三个方法只能在同步方法或者同步代码块中使用,否则会抛出异常。
线程间通信
使用wait()方法和notify()方法实现线程间通信。
代码如下:
public class CommunicateThread implements Runnable {
public static void main(String[] args) {
CommunicateThread thread = new CommunicateThread();
Thread ta = new Thread(thread, "线程ta");
Thread tb = new Thread(thread, "线程tb");
ta.start();
tb.start();
}
synchronized public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
if (i == 2) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i == 1) {
notify();
}
if (i == 4) {
notify();
}
}
}
}
运行结果如下:
线程ta0
线程ta1
线程ta2
线程tb0
线程tb1
线程tb2
线程ta3
线程ta4
线程tb3
线程tb4
运行结果分析:
1、在main()方法中启动线程ta和tb。
2、由于run()方法加了同步,线程ta先执行run()方法,执行for循环输出3条语句。
3、当i=2时,执行wait()方法,挂起当前线程,并释放共享资源的锁。
4、线程tb开始运行,执行for循环输出数据。
5、当i=1时,调用notify()方法,从等待队列唤醒一个线程。
6、ta等待tb释放对象锁,当i=2时,线程tb输出完3行数据后执行wait()方法,挂起线程,释放对象锁。
7、线程ta获得对象锁继续执行输出操作。
8、当i等于4的时候,调用notify()方法唤醒线程tb。
9、当ta执行完run()方法后释放对象锁,tb获得对象锁继续执行打印操作直至结束。
使用线程通信解决生产者和消费者问题的实例
生产者和消费者共享同一个仓库,并且生产者和消费者之间是相互依赖的。如何解决这个问题呢?使用线程同步可以阻止并发更新同一个共享资源,但是不能用它来实现不同线程之间的消息传递,要解决生产者消费者问题,需要使用线程通信。
实现步骤;
1、定义资源共享类
2、定义生产者线程类
3、定义消费者线程类
4、定义测试类
代码如下:
共享资源类:
public class SharedData {
private char c;
private boolean isProduced = false;
public synchronized void putShareChar(char c) {
if (isProduced) {
try {
System.out.println("消费者还未消费,因此生产者停止生产");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.c = c;
isProduced = true;
notify();
System.out.println("生产了产品" + c + "通知消费者消费...");
}
public synchronized char getShareChar() {
if (!isProduced) {
try {
System.out.println("生产者还未生产,因此消费者停止消费");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isProduced = false;
notify();
System.out.println("消费者消费了产品" + c + "通知生产者生产...");
return this.c;
}
}
生产者线程类:
public class Producer extends Thread{
private SharedData s;
Producer(SharedData s) {
this.s = s;
}
public void run() {
for (char ch = 'A';ch <= 'D'; ch++) {
try {
Thread.sleep((int)(Math.random()*3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
s.putShareChar(ch);
}
}
}
消费者线程类:
public class Consumer extends Thread {
private SharedData s;
Consumer(SharedData s) {
this.s = s;
}
public void run() {
char ch;
do {
try {
Thread.sleep((int)(Math.random()*3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ch = s.getShareChar();
} while (ch != 'D');
}
}
测试类:
public class CommunicationDemo {
public static void main(String[] args) {
SharedData s = new SharedData();
new Consumer(s).start();
new Producer(s).start();
}
}
输出结果如下:
生产了产品A通知消费者消费...
消费者消费了产品A通知生产者生产...
生产了产品B通知消费者消费...
消费者消费了产品B通知生产者生产...
生产了产品C通知消费者消费...
消费者还未消费,因此生产者停止生产
消费者消费了产品C通知生产者生产...
生产了产品D通知消费者消费...
消费者消费了产品D通知生产者生产...
结果分析:在上面的实例中,首先启动的是消费者线程,此时生产者线程还没有启动,也就是说此时消费者没有产品可以消费,所以消费者线程只能等待。在生产者生产了产品A以后就通知消费者过来消费,消费者线程停止等待,到仓库领取产品进行消费。而当生产者发现消费者还没有把上次生产的产品消费掉时它就停止生产,并通知消费者消费。当消费者消费了产品以后便通知生产者继续生产。