线程操作的实例

在线程操作中有个经典的案例程序,即生产者和消费者问题,生产者不断生产,消费者不断消费生产者生产的产品。
生产者生产出的信息方法一个区域之中,消费者从区域中将数据取出来,但是本程序中因为牵扯到线程运行的不确定性,所以会存在两个问题:
1. 假设生产者线程刚向数据空间添加了信息的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程将把信息的名称和上一个信息的内容联系在一起。
2. 生产者放了若干次的数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没等到生产者放入新的数据,又重复取出已经取出的数据。
- 程序的基本实现
因为程序中生产者不断生产的是信息,而消费者不断取出的也是信息,所以定义一个保存信息的类Info.java
【Info.java】

class Info
{
    private String name = "张三";
    private 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;
    }
}

Info类的组成非常简单,只包含了用于保存信息名称的name属性和用于保存信息生产者的content属性,因为生产者和消费者要同时操作一个空间的内容,所以生产者和消费者分别实现Runnable接口,并接收Info类的引用。
【生成者】

class Producer implements Runnable
{
    private Info info=null;
    public Producer(Info info){
        this.info=info;
    }
    public run(){
        boolean flag=false;
        for (int i=0;i<50 ;i++ )
        {
            if (flag)
            {
                this.info.setName("张三");
                try
                {
                    Thread.sleep(90);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                this.info.setContent("教师");
                flag=false;
            }else 
            {
                this.info.setName("zhangsan");
                try
                {
                    Thread.sleep(90);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                this.info.setContent("www.zhangsan.com");
                flag=true;
            }
        }
    }
}

在生产者类的构造方法中传入了Info类的实例化对象,然后在run()方法中循环50次以产生信息的具体内容,此外为了让读者更好的发现问题,在线程中加入了延迟操作。
【消费者】

class Consumer implements Runnable
{
    private Info info=null;
    public Consumer (Info info){
        this.info=info;
    }
    public void run(){
        for (int i=0;i<50 ;i++ )
        {
            try
            {
                Thread.sleep(90);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println(this.info.getName()+"-->"+this.info.getContent());
        }
    }
}

在消费者线程类中同样也接收了一个info对象的引用,并采用循环的方式取出50次信息并输出。
【测试程序】

public class ThreadCaseDemo01
{
    public static void main(String args[])
    {
        Info info=new Info();
        Producer pro=new Producer(info);
        Consumer con=new Consumer(info);
        new Thread(pro).start();
        new Thread(con).start();
    }
}

运行结果:

张三-->www.zhangsan.com
zhangsan-->教师
张三-->www.zhangsan.com
zhangsan-->教师
张三-->www.zhangsan.com
zhangsan-->教师
张三-->www.zhangsan.com
zhangsan-->教师
张三-->www.zhangsan.com
zhangsan-->教师
张三-->www.zhangsan.com
zhangsan-->教师
张三-->www.zhangsan.com
。。。。。
zhangsan-->教师
张三-->www.zhangsan.com
张三-->教师
张三-->www.zhangsan.com
张三-->教师
。。。。

从运行结果来看前面所提到的问题都出现了,下面先来解决第一个问题。

  • 问题解决1——加入同步
    如果要为操作加入同步,则可以通过定义同步方法的方式完成,即将设置名称和姓名定义成一个同步方法。
    【修改Info类】
    在info类中加入;
public synchronized void set(String name,String content){
        this.setName(name);
        try
        {
            Thread.sleep(300);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        this.setContent(content);
    }
    public synchronized void get(){
        try
        {
            Thread.sleep(300);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(this,getName()+"-->"+this.getContent());
    }

在类中加入了一个set()和get()方法,并使用synchronized关键字进行声明,因为现在不希望直接调用getter及setter方法,所以修改生产者和消费者类中的代码就行了
【修改生产者】

class Producer implements Runnable
{
    private Info info=null;
    public Producer(Info info){
        this.info=info;
    }
    public void run(){
        boolean flag=false;
        for (int i=0;i<50 ;i++ )
        {
            if (flag)
            {
                this.info.set("张三","教师");
                flag=false;
            }else 
            {
                this.info.set("zhangsan","www.zhangsan.com");
                flag=true;
            }
        }
    }
};

消费者的修改同理,不再给出。
运行部分结果:

张三-->教师
zhangsan-->www.zhangsan.com
张三-->教师
张三-->教师
zhangsan-->www.zhangsan.com
zhangsan-->www.zhangsan.com
zhangsan-->www.zhangsan.com
.....

从上面的输出结果可以看出信息错乱的问题已经得到解决,但是依然存在重复读取的问题,此时就可以使用Object类来帮忙了。

  • Object类对线程的支持——等待与唤醒
    从前面的学习中知道Object类是所有类的父类,在此类中有以下几种方法是对线程操作有所支持的。
方法类型描述
public final void wait() throws InterruptedException普通线程等待
public final void wait(long timeout) throws InterruptedException普通线程等待,并指定等待的最大时间,毫秒
public final void wait(long timeout,int nanos) throws InterruptedException普通线程等待,并指定等待的最长毫秒和纳秒
public final void notify()普通唤醒一个等待的线程
public final void notifyAll()普通唤醒全部等待的线程

从表中可以看出,可以将一个线程设置为等待状态,但是对于唤醒的操作却由notify()和notifyAll()两个方法。一般来说,所有等待的线程依照顺序进行排序,如果现在使用了notify()方法,则会唤醒第一个等待的线程执行,而如果使用了notifyAll()方法,哪个线程的优先级越高,哪个线程就有可能先执行。

  • 问题解决2——加入等待与唤醒
    如果想让生产者不重复生产,消费者不重复取走,则可以增加一个标志位,假设标志位为boolean型变量,如果标志位的内容为true,则表示可以生产,但是不能取走,此时线程执行到了消费者线程则应该等待,如果标志位的内容为false,则表示可以取走,但是不能生产,如果生产者线程运行,则消费者线程应该等待。
    要完成上述功能,直接在Info类中进行修改就行。在Info类中加入标志位,并通过判断标志位来完成等待与唤醒的操作。
    【修改Info类】
class Info
{
    private String name = "张三";
    private String content= "教师";
    private boolean flag=false;
    public synchronized void set(String name,String content){
        if (!flag)
        {
            try
            {
                super.wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        this.setName(name);
        try
        {
            Thread.sleep(300);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        this.setContent(content);
        flag=false;
        super.notify();
    }
    public synchronized void get(){
        if (flag)
        {
            try
            {
                super.wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        try
        {
            Thread.sleep(300);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(this.getName()+"-->"+this.getContent());
        flag =true;//可以进行生产
        super.notify();
    }

    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;
    }
};

运行效果:

张三-->教师
zhangsan-->www.zhangsan.com
张三-->教师
zhangsan-->www.zhangsan.com
张三-->教师
zhangsan-->www.zhangsan.com
张三-->教师
。。。。。

从程序运行的效果来看。一个生产者生产完成,就等待一个消费者取出,消费者取出一个就等待生产者进行生产,这样就避免了重复生产和重复取出的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aotulive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值