线程通信,在多线程系统中,不同的线程执行不同的任务;如果这些任务之间存在联系,那么执行这些任务的线程之间就必须能够通信,共同协调完成系统任务。
案例分析
在案例中明,蔬菜基地作为生产者,负责生产蔬菜,并向超市输送生产的蔬菜;消费者通过向超市购买获得蔬菜;超市怎作为生产者和消费者之间的共享资源,都会和超市有联系;蔬菜基地、共享资源、消费者之间的交互流程如下:
在这个案例中,为什么不设计成生产者直接与给消费者交互?让两者直接交换数据不是更好吗?选择先把数据存储到共享资源中,然后消费者再从共享资源中取出数据使用,中间多了一个环节不是更麻烦了?
其实不是的,设计成这样是有原因的,因为这样设计很好的体现了面向对象的低耦合的设计理念;通过这样实现的程序能更加符合人的操作理念,更加贴合现实环境;同时,也能很好的避免因生产者与消费者直接交互而导致的操作不安全的问题。
我们来对高耦合和低耦合做一个对比就会很直观了:
关于高耦合和低耦合的区别,电脑中主机中的集成显卡和独立显卡也是一个非常好的例子。
接下来我们使用多线程技术实现该案例,案例代码如下:
// VegetableBase.java
// 蔬菜基地
public class VegetableBase implements Runnable {
// 超市实例
private Supermarket supermarket = null;
public VegetableBase(Supermarket supermarket) {
this.supermarket = supermarket;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
supermarket.push("黄瓜", 1300);
System.out.println("push : 黄瓜 " + 1300);
} else {
supermarket.push("青菜", 1400);
System.out.println("push : 青菜 " + 1400);
}
}
}
}
// Consumer.java
// 消费者
public class Consumer implements Runnable {
// 超市实例
private Supermarket supermarket = null;
public Consumer(Supermarket supermarket) {
this.supermarket = supermarket;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
supermarket.popup();
}
}
}
// Supermarket.java
// 超市
public class Supermarket {
// 蔬菜名称
private String name;
// 蔬菜数量
private Integer num;
// 蔬菜基地想超市输送蔬菜
public void push(String name, Integer num) {
this.name = name;
this.num = num;
}
// 用户从超市中购买蔬菜
public void popup() {
// 为了让效果更明显,在这里模拟网络延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("蔬菜:" + this.name + ", " + this.num + "颗。");
}
}
// 案例应用入口
public class App {
public static void main(String[] args) {
// 创建超市实例
Supermarket supermarket = new Supermarket();
// 蔬菜基地线程启动, 开始往超市输送蔬菜
new Thread(new VegetableBase(supermarket)).start();
new Thread(new VegetableBase(supermarket)).start();
// 消费者线程启动,消费者开始购买蔬菜
new Thread(new Consumer(supermarket)).start();
new Thread(new Consumer(supermarket)).start();
}
}
运行该案例,打印出运行结果,外表一片祥和,可还是被敏锐的发现了问题,问题如下所示:
在一片看似祥和的打印结果中,出现了一个很不祥和的特例,生产基地在输送蔬菜时,黄瓜的数量一直都是1300颗,青菜的数量一直是1400颗,但是在消费者消费时却出现了蔬菜名称是黄瓜的,但数量却是青菜的数量的情况。
之所以出现这样的问题,是因为在本案例共享的资源中,多个线程共同竞争资源时没有使用同步操作,而是异步操作,今儿导致了资源分配紊乱的情况;
需要注意的是,并不是因为我们在案例中使用Thread.sleep();模拟网络延迟才导致问题出现,而是本来就存在问题,使用Thread.sleep();只是让问题更加明显。
在本案例中需要解决的问题有两个,分别如下:
针对问题一解决方案:保证蔬菜基地在输送蔬菜的过程保持同步,中间不能被其他线程(特别是消费者线程)干扰,打乱输送操作;直至当前线程完成输送后,其他线程才能进入操作,同样的,当有线程进入操作后,其他线程只能在操作外等待。
所以,技术方案可以使用同步代码块/同步方法/Lock机制来保持操作的同步性。
针对问题二的解决方案:
技术方案:使用线程中的等待和唤醒机制。
同步操作,分为同步代码块和同步方法两种。详情可查看我的另外一篇关于多线程的文章:Java 线程不安全分析,同步锁和Lock机制,哪个解决方案更好
在java.lang.Object类中提供了用于操作线程通信的方法,详情如下:
注意:上述方法只能被同步监听锁对象来调用,否则发生 IllegalMonitorStateException。
wait和notify方法应用实例
假设A线程和B线程共同操作一个X对象(同步锁),A、B线程可以通过X对象的wait和notify方法来进行通信,流程如下:
基于上述机制,我们就可以使用同步操作 + wait和notify方法来解决案例中的问题了,重新来实现共享资源——超市对象:
// 超市
public class Supermarket {
// 蔬菜名称
private String name;
// 蔬菜数量
private Integer num;
// 超市是否为空
private Boolean isEmpty = true;
// 蔬菜基地向超市输送蔬菜
public synchronized void push(String name, Integer num) {
try {
// 超市有货时,不再输送蔬菜,而是要等待消费者获取
while (!isEmpty) {
this.wait();
}
this.name = name;
this.num = num;
isEmpty = false;
this.notify(); // 唤醒另一个线程
} catch(Exception e) {
}
}
// 用户从超市中购买蔬菜
public synchronized void popup() {
try {
// 超市无货时,不再提供消费,而是要等待蔬菜基地输送
while (isEmpty) {
this.wait();
}
// 为了让效果更明显,在这里模拟网络延迟
Thread.sleep(1000);
System.out.println("蔬菜:" + this.name + ", " + this.num + "颗。");
isEmpty = true;
this.notify(); // 唤醒另一线程
} catch (Exception e) {
}
}
}
由于wait和notify方法,只能被同步监听锁对象来调用,否则发生 IllegalMonitorStateException。从Java 5开始,提供了Lock机制,同时还有处理Lock机制的通信控制的Condition接口。Lock机制没有同步锁的概念,也就没有自动获取锁和自动释放锁的这样的操作了。
因为没有同步锁,所以Lock机制中的线程通信就不能调用wait和notify方法了;同样的,Java 5 中也提供了解决方案,因此从Java5开始,可以:
Lock和Condition接口的性能也比同步操作要高很多,所以这种方式也是我们推荐使用的方式。
我们可以使用Lock机制和Condition接口 方法来解决案例中的问题,重新来实现的共享资源——超市对象,代码如下:
// 超市
public class Supermarket {
// 蔬菜名称
private String name;
// 蔬菜数量
private Integer num;
// 超市是否为空
private Boolean isEmpty = true;
// lock
private final Lock lock = new ReentrantLock();
// Condition
private Condition condition = lock.newCondition();
// 蔬菜基地向超市输送蔬菜
public synchronized void push(String name, Integer num) {
lock.lock(); // 获取锁
try {
// 超市有货时,不再输送蔬菜,而是要等待消费者获取
while (!isEmpty) {
condition.await();
}
this.name = name;
this.num = num;
isEmpty = false;
condition.signalAll();
} catch(Exception e) {
} finally {
lock.unlock(); // 释放锁
}
}
// 用户从超市中购买蔬菜
public synchronized void popup() {
lock.lock();
try {
// 超市无货时,不再提供消费,而是要等待蔬菜基地输送
while (isEmpty) {
condition.await();
}
// 为了让效果更明显,在这里模拟网络延迟
Thread.sleep(1000);
System.out.println("蔬菜:" + this.name + ", " + this.num + "颗。");
isEmpty = true;
condition.signalAll();
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
完结,老夫虽不正经,但老夫一身的才华!关注我,获取更多编程科技知识。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。