实际生活中,需要操作共享的某个资源(水池),但是对这个共享资源操作方式不同(部分是注水【生产】、部分是抽水【消费】)。把这种现象我们可以称为生产和消费模型。
生产:它可以采用部分线程进行模拟。多个线程同时给水池中注水。
消费:它可以采用部分线程进行模拟。多个线程同时从水池中抽水。
对资源的不同的操作方式,每种方式都可以让部分线程去负责。多个不同的线程,他们对相同的资源(超市、水池等)操作方式不一致。
这个时候我们不能使用一个run方法对线程的任务进行封装。所以这里就需要定义不同的线程人物类,描述不同的线程任务。
通过不同的线程操作,来控制同一个资源,这种现象就属于生产消费模型
简单是实现生产消费模型
创建公共资源类
package com.test1;
/**
* 被多个线程操作的共享数据的共享资源类
* @author zxc
*
*/
public class Resource {
//注水的方法
public void add(){
}
//抽水的方法
public void delete(){
}
}
生产共享资源的目标类
package com.test1;
/**
* 生产任务类
* @author zxc
*
*/
public class ShengChan implements Runnable{
//定义共享资源对象
private Resource resource;
//通过构造方法传入共享资源对象
public ShengChan(Resource resource){
this.resource=resource;
}
@Override
public void run() {
//访问共享资源的生产方法
resource.add();
}
}
消费资源共享的目标类
package com.test1;
/**
* 消费共享资源的目标类
* @author zxc
*
*/
public class XiaoFei implements Runnable{
//定义共享资源对象
private Resource resource;
//通过构造方法传入共享资源对象
public XiaoFei(Resource resource){
this.resource=resource;
}
@Override
public void run() {
//访问共享资源的消费方法
resource.delete();
}
}
测试主类
package com.test1;
public class TestMain {
public static void main(String[] args) {
//创建共享资源类对象
Resource resource=new Resource();
//创建生产共享资源的目标类对象
ShengChan sc=new ShengChan(resource);
//创建消费共享资源的目标对象
XiaoFei xf=new XiaoFei(resource);
//创建生产者线程对象
Thread scthread=new Thread(sc);
//创建消费者线程对象
Thread xfthread=new Thread(xf);
//启动生产和消费线程
scthread.start();
xfthread.start();
}
}
修改Resource为add()/delete()添加具体实现动作
package com.test1;
/**
* 被多个线程操作的共享数据的共享资源类
* @author zxc
*
*/
public class Resource {
//保存共享资源的数据[水池]
private Object objs[]=new Object[1];
//记录生产和消费的次数
private int num=1;
//注水的方法
public void add(){
objs[0]="水"+num;
System.out.println(Thread.currentThread().getName()+"正要注入的水是:"+objs[0]);
num++;
}
//抽水的方法
public void delete(){
System.out.println(Thread.currentThread().getName()+"抽出的水是:"+objs[0]);
objs[0]=null;
}
}
修改生产者的目标类为注水方法添加循环,以达到持续追谁的目标
package com.test1;
/**
* 生产任务类
* @author zxc
*
*/
public class ShengChan implements Runnable{
//定义共享资源对象
private Resource resource;
//通过构造方法传入共享资源对象
public ShengChan(Resource resource){
this.resource=resource;
}
@Override
public void run() {
//持续注水
for(int i=1;i<=50;i++){
//访问共享资源的生产方法
resource.add();
}
}
}
修改消费者的目标类为抽水方法添加循环,以达到持续抽水的目标
package com.test1;
/**
* 消费共享资源的目标类
* @author zxc
*
*/
public class XiaoFei implements Runnable{
//定义共享资源对象
private Resource resource;
//通过构造方法传入共享资源对象
public XiaoFei(Resource resource){
this.resource=resource;
}
@Override
public void run() {
//持续抽水
for(int i=1;i<=50;i++){
//访问共享资源的消费方法
resource.delete();
}
}
}
有时会消费者抽水为null的情况:
有两个线程分别是生产者负责注水的线程和消费者负责抽水的线程。
假设cpu在消费者线程上,那么消费者打印完抽水"水1"的情况下,还没有将数组空间赋值为null之前,cpu切换到生产者,生产者将水注到数组空间中之后,打印出正要注进入的水是:水2,cpu有切回到消费者线程上,消费者线程就会将数组空间立刻赋值为null。cpu如果再切回到生产者线程上,执行了注水次数加1之后。cpu如果在切回到消费者线程上,这时消费者线程就会输出抽水为null的情况
有时会出现生产者注水为null的情况:
有两个线程分别是生产者负责注水的线程和消费者负责抽水的线程。
假设cpu在消费者线程上,那么消费者正要打印了抽水为null的情况下,还没有将数组空间赋值为null之前,cpu切换到生产者,生产者将水注到数组空间中之后,还没有打印,cpu又切回到消费者线程上,消费者线程就会将数组空间卢克赋值为null。cpu如果再切回到生产者线程上,打印出来的注水就是null。
上面这两个问题就是因为当前线程正在访问共享资源的时候,其他线程也可以访问共享资源所产生的。所以线程操作共享数据时,需要进行线程同步。
线程同步能够保证注水的时候不能抽水,或者抽水的时候不能给当前这个空间注水。
修改Resource为注水和抽水方法添加同步代码块保证注水的时候不能抽水,或者抽水的时候不能给当前这个空间注水