多线程操作中的经典案例,生产者 — 消费者模型,生产者生产产品,消费者取走产品。
程序的基础模型
//定义产品类,保存生产出的产品
class Message {
private String title ;
private String content ;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
//定义生产产品类,生产两类产品
class Producer implements Runnable {
private Message msg = null;
public Producer(Message msg) {
this.msg = msg;
}
public void run() {
for(int i =0 ; i < 50; i++) {
if(i % 2 == 0) {
this.msg.setTitle("张三");
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
this.msg.setContent("是一位人民教师。");
}
else {
this.msg.setTitle("李苏");
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
this.msg.setContent("是维修工人。");
}
}
}
}
//消费者类,取走生产者生产出的数据
class Consumer implements Runnable {
private Message msg = null;
public Consumer(Message msg) {
this.msg = msg;
}
public void run() {
for(int x = 0; x < 50; x++) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(msg.getTitle() + " --> " + msg.getContent());
}
}
}
//java主类,运行程序
public class ThreadDemo {
public static void main(String[] args) {
Message msg = new Message();
new Thread(new Producer(msg)).start();
new Thread(new Consumer(msg)).start();
}
}
程序执行结果如下:
李苏 --> 是一位人民教师。
张三 --> 是维修工人。
张三 --> 是维修工人。
李苏 --> 是一位人民教师。
张三 --> 是维修工人。
李苏 --> 是一位人民教师。
张三 --> 是维修工人。
李苏 --> 是一位人民教师。
张三 --> 是维修工人。
省略…
可以看出产生了两个问题,数据错位 与 重复数据,此时生产者刚添加了标题还未添加内容,程序就切换到了消费者线程,导致本次内容与上一次的联系在了一起,造成了数据错位。
而当生产者放入了多次数据,消费者才开始取出数据,或是消费者还没有等生产者放入新数据又开始取出已经取出过的数据。
引入同步,改进案例
/*
* 针对生产 - 消费者模型的解决办法,解决数据错位问题
* 但是此种会产生重复问题,消费者多次取走了相同生产产品
*/
class Message {
private String title ;
private String content ;
//设置get与set为同步方法,同时将title与content绑定式的写入与取出
public synchronized void set(String title, String content) {
this.title = title;
this.content = content;
}
public synchronized String get() {
return this.title + " -- " + this.content;
}
}
class Producer implements Runnable {
private Message msg = null;
public Producer(Message msg) {
this.msg = msg;
}
public void run() {
for(int i =0 ; i < 50; i++) {
if(i % 2 == 0) {
this.msg.set("张三", "是一位人民教师。");
}
else {
this.msg.set("李四", "是维修工人。");
}
}
}
}
class Consumer implements Runnable {
private Message msg = null;
public Consumer(Message msg) {
this.msg = msg;
}
public void run() {
for(int x = 0; x < 50; x++) {
System.out.println(msg.get());
}
}
}
public class Plan {
public static void main(String[] args) {
Message msg = new Message();
new Thread(new Producer(msg)).start();
new Thread(new Consumer(msg)).start();
}
}
程序执行结果如下:
李四 – 是维修工人。
李四 – 是维修工人。
李四 – 是维修工人。
李四 – 是维修工人。
李四 – 是维修工人。
李四 – 是维修工人。
省略…
可以看出解决了数据错位,但是仍然产生了数据重复错误。为了解决重复问题需要引入线程的等待与唤醒机制。
利用等待与唤醒机制解决重复问题
class Message {
private String title;
private String content;
private boolean flag = false;
public synchronized void set(String title, String content) {
if (this.flag == true) { // 有产品无法生产,等待被消费
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.title = title;
this.content = content;
// 生产出产品后标记设为true,表示此时有产品
this.flag = true;
super.notify();
}
public synchronized String get() {
if (this.flag == false) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 此处使用try块,直接return会导致后面语句失效,取出产品后设置标记为false
try {
return this.title + " -- " + this.content;
} finally {
this.flag = false;
super.notify();
}
}
}
class Producer implements Runnable {
private Message msg = null;
public Producer(Message msg) {
this.msg = msg;
}
public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 == 0) {
this.msg.set("张三", "是一位人民教师。");
} else {
this.msg.set("李四", "是维修工人。");
}
}
}
}
class Consumer implements Runnable {
private Message msg = null;
public Consumer(Message msg) {
this.msg = msg;
}
public void run() {
for (int x = 0; x < 50; x++) {
System.out.println(msg.get());
}
}
}
public class FinallPlan {
public static void main(String[] args) {
Message msg = new Message();
new Thread(new Producer(msg)).start();
new Thread(new Consumer(msg)).start();
}
}
Object类对多线程的支持
public final void wait() throws InterruptedException
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
public final void notify()
唤醒在此对象监视器上等待的单个线程。
如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
public final void notifyAll()
唤醒在此对象监视器上等待的单个线程。
唤醒在此对象监视器上等待的所有线程。优先级高的可能先执行。
本例中添加了一个标志属性位flag,通过判断此标记控制等待与唤醒操作,解决了重复问题。
这种处理形式就是在进行多线程开发过程之中最原始的处理方案,整个的同步、等待、唤醒机制都有开发者自行控制