Java编程--多线程(生产者和消费者问题)
知识点:
1. 生产者和消费者问题的产生;
2. Object类对多线程的支持。
一.问题的引出
生产者和消费者指的是两个不同的线程类对象,操作同一资源的情况。具体操作流程如下:
(1)生产者负责生产数据,消费者负责取走数据;
(2)生产者每生产完一组数据之后,消费者就要取走一组数据。
假设要生产的数据如下:
(1)第一组数据:name = Jack ,content = 大学生
(2)第二组数据:name = Mike ,content = 中学生
【范例】程序基本模型。
存储数据的类Info:
class Info {
String name;
String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
生产者类Producer:
class Producer implements Runnable {
private Info info;
public Producer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
this.info.setName("Jack");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.info.setContent("大学生");
} else {
this.info.setName("Mike");
this.info.setContent("中学生");
}
}
}
}
消费者类Customer:
class Customer implements Runnable {
private Info info;
public Customer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.info.getName() + "---" + this.info.getContent());
}
}
}
主函数:
public class Test {
public static void main(String[] args) throws Exception {
Info info = new Info();
new Thread(new Producer(info)).start();
new Thread(new Customer(info)).start();
}
}
运行结果:
通过以上代码发现两个严重问题:
(1)数据错位:不再是所需要的完整数据;
(2)数据重复取出,数据重复设置。
二.同步处理
数据的错位完全由非同步的操作所造成的,所以应该使用同步处理。因为取和设置是两个不同的操作,要想进行同步设置,就需要将其定义在一个类里面。
【案例】给程序加上同步。
class Info {
String name;
String content;
public synchronized void set(String name, String content) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.content = content;
}
public synchronized void get() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + "---" + this.content);
}
}
class Producer implements Runnable {
private Info info;
public Producer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
this.info.set("Jack","大学生");
} else {
this.info.set("Mike","中学生");
}
}
}
}
class Customer implements Runnable {
private Info info;
public Customer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.info.get();
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
Info info = new Info();
new Thread(new Producer(info)).start();
new Thread(new Customer(info)).start();
}
}
运行结果:
此时,数据的错位问题已经得到了很好的解决,但是重复操作问题更加严重了。
三.Object类支持
若要想实现整个代码的操作,必须加入等待与唤醒机制。在Object里面提供有专门的处理方法。
(1)等待:
public final void wait() throws InterruptedException
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
(2)唤醒第一个等待线程:
public final void notify()
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
(3)唤醒全部等待线程:(哪个优先级高就先执行)
public final void notifyAll()
唤醒在此对象监视器上等待的所有线程。
【范例】解决程序问题。
class Info {
String name;
String content;
private boolean flag = true;
// flag为true表示可以生产,但是不能取走;false表示可以取走,但是不能生产
public synchronized void set(String name, String content) {
// 重复进入到set()方法里,发现不能生产,故需要等待
if (this.flag == false) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.content = content;
this.flag = false; // 生产完成后修改生产标记
super.notify(); // 唤醒其他等待线程
}
public synchronized void get() {
if (this.flag == true) { // 还没生产
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + "---" + this.content);
this.flag = true;
super.notify();
}
}
class Producer implements Runnable {
private Info info;
public Producer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
this.info.set("Jack", "大学生");
} else {
this.info.set("Mike", "中学生");
}
}
}
}
class Customer implements Runnable {
private Info info;
public Customer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.info.get();
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
Info info = new Info();
new Thread(new Producer(info)).start();
new Thread(new Customer(info)).start();
}
}
运行结果:
面试题:请解释sleep()与wait()的区别?
(1)sleep()是Thread类定义的方法,wait()是Object类定义的方法。
(2)sleep()可以设置休眠时间,时间一到自动唤醒,而wait()需要等待notify()进行唤醒。