内容学习于:edu.aliyun.com


1. 生产者与消费者基本模型

  在多线程的开发过程之中最为著名的案例就是生产者与消费者操作,该操作的主要流程如下:

  • 生产者负责信息内容的生产:
  • 每当生产者生产完成一项完整的信息之后消费者要从这里面取走信息;
  • 如果生产者没有生产则消费者要等待它生产完成,如果消费者还没有对信息进行消费,则生产者应该等待消费处理完成后再继续生产。

  可以将生产者与消费者定义为两个独立的线程类对象,但是对于现在生产的数据,可以使用如下的组成:

  • 数据一: title = 王建、content = 宇宙大帅哥;
  • 数据二: title = 小高、content = 猥琐第一人;

  既然生产者与消费者是两个独立的线程,那么这两个独立的线程之间就需要有一个数据的保存集中点,那么可以单独定义一个Message类实现数据的保存。
  如下图所示:

代码:

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 Producer implements Runnable {
    private Message msg;

    public Producer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                this.msg.setTitle("王建");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.setContent("宇宙大帅哥");
            } else {
                this.msg.setTitle("小高");
                this.msg.setContent("猥琐第一人");
            }
        }
    }
}

class Consumer implements Runnable {
    private Message msg;

    public Consumer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.msg.toString());
        }
    }
}

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

    @Override
    public String toString() {
        return "Message{" +
                "title='" + title + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

结果:

Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘null’}
Message{title=‘王建’, content=‘猥琐第一人’}
Message{title=‘王建’, content=‘猥琐第一人’}
Message{title=‘王建’, content=‘猥琐第一人’}

2. 解决生产者-消费者同步问题

  如果要解决问题,首先解决的就是数据同步的处理问题,如果要想解决数据同步最简单的做法是使用synchronized关键字定义同步代码块或同步方法,于是这个时候对于同步的处理就可以直接在Message类中完成。

代码:

class Message {//负责存储消息
    private String title;
    private String content;

    //设置同步操作
    public synchronized void set(String title, String content) {//s
        this.title = title;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.content = content;
    }

    public synchronized String get() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Message{" +
                "title='" + title + '\'' +
                ", content='" + content + '\'' +
                '}';
    }

}


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 Producer implements Runnable {
    private Message msg;

    public Producer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                this.msg.set("王建", "宇宙大帅哥");
            } else {
                this.msg.set("小高", "猥琐第一人");
            }
        }
    }
}

class Consumer implements Runnable {
    private Message msg;

    public Consumer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.msg.get());
        }
    }
}

结果:

Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘小高’, content=‘猥琐第一人’}

  在进行同步处理的时候肯定需要有一个同步的处理对象,那么此时肯定要将同步操作交由Message类处理是最合适的。这个时候发现数据已经可以正常的保持一致了,但是对于重复操作的问题依然存在。

3. 利用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()会唤醒所有等待的线程,哪个线程的优先级高就有可能先执行。
  对于当前的问题主要的解决应该通过Message类完成处理。

代码:

class Message {//负责存储消息
    private String title;
    private String content;
    private boolean statue = true;//表示当前允许的状态
    //statue为true,则表示允许生产,但是不允许消费
    //statue为flase,则表示允许消费,但是不允许生产

    //设置同步操作
    public synchronized void set(String title, String content) {
        if (!this.statue) {//如果还未消费
            try {
                super.wait();//生产线程需要等待消费
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.title = title;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.content = content;
        this.statue = false;//生产完成允许消费
        super.notify();//唤醒可能等待的消费线程
    }

    public synchronized String get() {
        if (this.statue) {//如果不允许消费
            try {
                super.wait();//消费线程陷入等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            return "Message{" +
                    "title='" + title + '\'' +
                    ", content='" + content + '\'' +
                    '}';
        } finally {//不管如何都要执行
            this.statue = true;//完成消费,将允许生产
            super.notify();//唤醒生产线程
        }

    }

}


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 Producer implements Runnable {
    private Message msg;

    public Producer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                this.msg.set("王建", "宇宙大帅哥");
            } else {
                this.msg.set("小高", "猥琐第一人");
            }
        }
    }
}

class Consumer implements Runnable {
    private Message msg;

    public Consumer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.msg.get());
        }
    }
}

结果:

Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘小高’, content=‘猥琐第一人’}
Message{title=‘王建’, content=‘宇宙大帅哥’}
Message{title=‘小高’, content=‘猥琐第一人’}

  这种处理形式就是在进行多线程开发过程之中最原始的处理方案,整个的等待、同步、唤醒机制都由开发者自行通过原生代码实现控制。.