java的多线程问题:生产消费者模式实例
需求说明
生产一个对象,消费一个对象;如果此对象没有被消费,则不能生产,只能等待。如果要消费对象,但是此对象还没有生产,则不能消费,只有等待;
如果消费了一个对象,则通知生产者,进行生产;
如果生产了一个对象,则通知消费者进行消费;
这里是使用Student类的get()和set()方法进行说明;
如果Student设置了姓名和年龄,则意味着生产了对象;
如果Student输出了姓名和年龄,则意味着消费了一个对象;创建了四个类,Student,SetThread,GetThread,和一个测试类。本实例会一步一步说明通信的问题及原因和解决办法。
第一步 不加锁的情况下进行通信
1.学生类:
package com.wy.communication_start;
public class Student {
String name;
int age;
}
SetThread类
package com.wy.communication_start;
public class SetThread implements Runnable {
private int x = 0;
@Override
public void run() {
Student s = new Student();
while (true) {
if (x % 2 == 0) {
s.name = "A";
s.age = 8;
} else {
s.name = "B";
s.age = 66;
}
x++;
}
}
}
GetThread类:
package com.wy.communication_start;
public class GetThread implements Runnable {
@Override
public void run() {
Student s = new Student();
while (true) {
System.out.println(s.name + "--" + s.age);
}
}
}
测试类:
package com.wy.communication_start;
public class StudentDemo {
public static void main(String[] args) {
//创建学生对象
Student s = new Student();
//创建设置线程和获取线程
SetThread st = new SetThread();
GetThread gt = new GetThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//开启线程
t1.start();
t2.start();
/*
* 输出的结果为 null--0
* 原因:GetThread类中的run()方法,重新new Student(),这个时候,是没有对这个对象进行设置的
* 改进版:让他们调用的是同一个对象
*/
}
}
结果:上面代码运行结果是 null–0;
这是因为获取线程中又重新创建了对象,但是没有对对象设置值。
改进版第二步让线程操作的是同一个Student;
Student不变。
SetThread类
package com.wy.communication_two;
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
s.name = "A";
s.age = 8;
} else {
s.name = "B";
s.age = 66;
}
x++;
}
}
}
GetThread类:
package com.wy.communication_two;
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
System.out.println(s.name + "--" + s.age);
}
}
}
测试类:
package com.wy.communication_two;
public class StudentDemo {
public static void main(String[] args) {
//创建学生对象
Student s = new Student();
//创建设置线程和获取线程
GetThread gt = new GetThread(s);
SetThread st = new SetThread(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//开启线程
t1.start();
t2.start();
}
}
结果: 出现了数据错乱的问题
原因:虽然两个线程是对同一个对象进行操作。但是由于cpu处理是具有原子性的,因此可能是第一个线程刚设置好了姓名,get线程就抢占到了执行权,出现了数据错乱问题.
改进版第三:给设置线程和获取线程加同步代码块。
SetThread类:
package com.wy.communication_three;
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (x % 2 == 0) {
s.name = "A";
s.age = 6;
} else {
s.name = "B";
s.age = 88;
}
x++;
}
}
}
}
GetThread类:
package com.wy.communication_three;
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
System.out.println(s.name + "--" + s.age);
}
}
}
}
结果:出现新的问题:总是出来一段相同的数据
原因:get线程如果想抢到执行权,则在一段时间内,会一直知行如果set线程抢到执行权,则也会在一段时间内一直设置,这个时候,如果下一个get线程抢占到执行权,则会出现另一一段数据
改进方法:使用线程的等待和唤醒机制
SetThread类:
package com.wy.communication_four;
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
boolean flag = false;
@Override
public void run() {
while (true) {
synchronized (s) {
if(s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (x % 2 == 0) {
s.name = "A";
s.age = 6;
} else {
s.name = "B";
s.age = 88;
}
x++;
//改变flag的值
s.flag = true;
//唤醒等待的线程
s.notify();
}
}
}
}
GetThread类:
package com.wy.communication_four;
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag)
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(s.name + "--" + s.age);
//取完数据后更改flag和唤醒等待设置的线程
s.flag = false;
s.notify();
}
}
}
}
结果:打印一个A–6
打印一个B–88
最后优化代码:
Student类
package com.wy.communication;
public class Student {
private String name;
private int age;
boolean flag;
public synchronized void setInfo(String name,int age){
if(this.flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.age = age;
this.flag = true;
this.notify();
}
public synchronized void getInfo(){
if(!this.flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.name+" "+this.age);
flag = false ;
this.notify();
}
}
设置线程
package com.wy.communication;
public class ThreadSet extends Thread{
private Student s ;
private int x=0;
public ThreadSet(Student s) {
this.s = s;
}
@Override
public void run() {
while(true){
if(x % 2 ==0){
s.setInfo("A", 6);
}else{
s.setInfo("B", 88);
}
x++;
}
}
}
获取线程
package com.wy.communication;
public class ThreadGet extends Thread{
private Student s ;
public ThreadGet(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
s.getInfo();
}
}
}
测试类
package com.wy.communication;
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
ThreadSet set = new ThreadSet(s);
ThreadGet get = new ThreadGet(s);
set.start();
get.start();
}
}
至此,关于线程通信问题到此结束。