勿以惡小而爲之,勿以善小而不爲--------------------------劉備
勸諸君,多行善事積福報,莫作惡
上一章簡單介紹了 同步和死鎖(三),如果沒有看過,請觀看上一章
一. 生產者和消費者問題
生產者和消費者問題,是多線程中很常見的一種問題, 生產者生產產品,消費者消費產品,但同時,如果產品過多時,生產者暫停生產,讓消費者抓緊消費, 如果產品沒有時,消費者不能消費,讓生產者抓緊生產。
生產的產品是一個實體類,如 Info 類, 裏面有 name 和 content 兩種屬性。 注意,生產產品時,這兩個屬性應該是配套的, 消費時,也應該是配套的。
二. 生產者和消費者問題第一版
二.一 消息實體類 Info
很正常的 name 和 content 兩種屬性構成的 pojo.
public class Info {
private String name;
private String content;
public Info(){
}
public Info(String name,String content){
this.name=name;
this.content=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;
}
@Override
public String toString() {
return "Info{" +
"name='" + name + '\'' +
", content='" + content + '\'' +
'}';
}
}
二.二 生產者 Productor
public class Productor implements Runnable {
private Info info;
//傳進來對象
public Productor(Info info){
this.info=info;
}
private boolean isFirst=true;
@Override
public void run() {
for(int i=0;i<50;i++){
if(isFirst){ //是第一個,那麼就生產第一個消息
this.info.setName("兩個蝴蝶飛");
//生產消息,需要時間
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.info.setContent("這是兩個蝴蝶飛");
isFirst=false;
}else{
this.info.setName("老蝴蝶");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.info.setContent("這是老蝴蝶");
isFirst=true;
}
}
}
}
二.三 消費者 Consumer
public class Consumer implements Runnable {
private Info info;
public Consumer(Info info){
this.info=info;
}
@Override
public void run() {
for(int i=0;i<50;i++){
//消費也需要時間
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(info.toString());
}
}
}
二.四 主程序測試 Demo
public class Demo {
public static void main(String[] args) {
//定義產品對象
Info info=new Info();
//生產 和消費用同一個對象
Productor productor=new Productor(info);
//消費
Consumer consumer=new Consumer(info);
Thread thread=new Thread(productor);
Thread thread1=new Thread(consumer);
//啓動
thread.start();
thread1.start();
}
}
二.五 測試運行,發現問題
發現,取出的產品竟然亂套了。 這是沒有同步的原因。
在設置內容時,需要進行同步, 在取出來內容時,也需要同步。
同步,需要放置在 Info 對象中進行處理。
三. 生產者和消費者同步 第二版
將上面的程序進行改寫。
三.一 同步產品類 Info
裏面有兩個同步的方法。
public class Info {
private String name;
private String content;
public Info(){
}
//設置產品
public synchronized void setInfo(String name,String content){
this.setName(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content);
}
//取出產品
public synchronized void getInfo(){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"---->"+this.getContent());
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return this.content;
}
public void setContent(String content) {
this.content = content;
}
}
三.二 生產者 Productor
public class Productor implements Runnable {
private Info info=null;
private boolean isFirst=true;
public Productor(Info info){
this.info=info;
}
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("輸出 i:"+i+",isFirst:"+isFirst);
if(isFirst){ //是第一個,那麼就生產第一個消息
this.info.setInfo("兩個蝴蝶飛","這是兩個蝴蝶飛");
isFirst=false;
}else{
this.info.setInfo("老蝴蝶","這是老蝴蝶");
isFirst=true;
}
}
}
}
三.三 消費者 Consumer
public class Consumer implements Runnable {
private Info info=null;
public Consumer(Info info){
this.info=info;
}
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("");
//獲取信息
this.info.getInfo();
}
}
}
三.四 主程序測試Demo
public class Demo {
public static void main(String[] args) {
//定義生產者
Info info=new Info();
//生產
Productor productor=new Productor(info);
//消費
Consumer consumer=new Consumer(info);
Thread thread=new Thread(productor);
Thread thread1=new Thread(consumer);
//啓動
thread.start();
thread1.start();
}
}
三.五 測試運行,發現問題
常常會先生產這一部分,後生產那一部分, 並不是生產一個,拿一個。
需要用 Object 對象的 wait() 和 notify(), notifyAll() 方法進行設置。
四. 等待和喚醒 生產者和消費者第三版
在Info 對象裏面設置一個標識位, 如果生產了一件產品,那麼就生產者先等待,讓消費者去消費好之後,再重新生產,避免多次生產一件產品。
四.一 等待和喚醒 Info
package com.yjl.thread.cp.c3;
/**
* package: com.yjl.thread.cp
* className: Info
* Description: 請輸入相應的描述
*
* @author : yuezl
* @Date :2020/6/13 12:53
*/
public class Info {
private String name;
private String content;
//定義標識位
private boolean flag=false;
public Info(){
}
public synchronized void setInfo(String name,String content){
if(flag){ //爲真
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content);
flag=true;
//當前只有一個線程,喚醒一個線程, 也可以直接喚醒多個。
super.notifyAll();
}
public synchronized void getInfo(){
if(!flag){
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"---->"+this.getContent());
flag=false;
//當前只有一個線程,喚醒一個線程, 也可以直接喚醒多個
super.notifyAll();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return this.content;
}
public void setContent(String content) {
this.content = content;
}
}
生產者,消費者和 Demo 均與 第二版的內容一樣。
運行程序,控制檯打印輸出
發現,可以做到每次生產一個產品,取出一個產品的功能了。
但是,這種情況下,只有一個生產者和一個消費者,如果都有兩個呢?
其實,我們可以先創建一個倉庫, 當生產時,可以將產品放置到倉庫裏面, 當倉庫滿時,纔不讓繼續生產, 如果倉庫沒有滿,就繼續生產。 當倉庫沒有產品時,就不讓繼續消費了。
五. 倉庫版 生產者和消費者 第四版
五.一 產品 Info 類
Info 就不需要進行等待和喚醒了。
public class Info {
private String name;
private String content;
public Info(){
}
public synchronized void setInfo(String name,String content){
this.setName(name);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content);
}
public synchronized void getInfo(){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"---->"+this.getContent());
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return this.content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Info{" +
"name='" + name + '\'' +
", content='" + content + '\'' +
'}';
}
}
五.二 倉庫 Storage
public class Storage {
private static final int DEFAULT_SIZE=10;
//定義一個集合,用於存儲對象信息。
private volatile List<Info> infoList=new ArrayList<Info>();
private volatile int count=0;
/**
* 進行生產
*/
public void produce(Info info){
synchronized (infoList) {
//超過出容易
if(infoList.size()>=DEFAULT_SIZE){
System.out.println("生產者:"+Thread.currentThread().getName()+"的物品不能放置進來,因爲倉庫已經滿了,");
try {
infoList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println();
count++;
System.out.println("生產者:"+Thread.currentThread().getName()+"生產了一件產品:"
+info.toString()+",已經放置在倉庫了,倉庫有:"+count+"件商品");
infoList.add(info);
infoList.notifyAll();
}
}
/**
*
*/
public synchronized Info consume(){
synchronized (infoList){
//爲 0了
if(infoList.size()<=0){
System.out.println("消費者:"+Thread.currentThread().getName()+"不能消費產品,因爲倉庫已經空了.");
try{
infoList.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
Info lastInfo=infoList.get(count-1);
infoList.remove(count-1);
count--;
System.out.println("消費者:"+Thread.currentThread().getName()+"消費了一件產品:"+lastInfo.toString()
+",還剩下"+count+"件產品");
//通知
infoList.notifyAll();
return lastInfo;
}
}
//public static List<Info> getInfoList() {
// return infoList;
// }
}
五.三 生產者 Productor
public class Productor implements Runnable {
private Storage storage;
private boolean isFirst=true;
//傳入倉庫
public Productor(Storage storage){
this.storage=storage;
}
@Override
public void run() {
for(int i=0;i<50;i++){
//定義產品
Info info=new Info();
System.out.println("生產者運行i:"+i);
if(isFirst){ //是第一個,那麼就生產第一個消息
info.setInfo("兩個蝴蝶飛","這是兩個蝴蝶飛");
isFirst=false;
}else{
info.setInfo("老蝴蝶","這是老蝴蝶");
isFirst=true;
}
//放置到倉庫裏面
storage.produce(info);
}
}
}
五.四 消費者 Consumer
public class Consumer implements Runnable {
private Storage storage;
public Consumer(Storage storage){
this.storage=storage;
}
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("消費者運行i:"+i);
//獲取信息
Info info=storage.consume();
//info.getInfo();
}
}
}
五.五 主程序測試 Demo
public class Demo {
public static void main(String[] args) {
//定義生產者
Storage storage=new Storage();
//生產
Productor productor=new Productor(storage);
//消費
Consumer consumer=new Consumer(storage);
Thread thread=new Thread(productor);
Thread thread1=new Thread(productor);
Thread thread2=new Thread(consumer);
Thread thread3=new Thread(consumer);
//啓動
thread.start();
thread1.start();
thread2.start();
thread3.start();
}
}
五.六 測試運行,發現問題
發現會生產一件,消費一件, 這是兩個生產者和兩個消費者。
如果是三個生產者和一個消費者呢?
public class Demo {
public static void main(String[] args) {
//定義生產者
Storage storage=new Storage();
//生產
Productor productor=new Productor(storage);
//消費
Consumer consumer=new Consumer(storage);
Thread thread=new Thread(productor);
Thread thread1=new Thread(productor);
Thread thread2=new Thread(consumer);
Thread thread3=new Thread(consumer);
//啓動
thread.start();
thread1.start();
new Thread(productor).start();
thread2.start();
// thread3.start();
}
}
運行程序,查看
倉庫滿了之後,就不能再繼續生產產品了。
如果一個生產者,三個消費者呢?
public class Demo {
public static void main(String[] args) {
//定義生產者
Storage storage=new Storage();
//生產
Productor productor=new Productor(storage);
//消費
Consumer consumer=new Consumer(storage);
Thread thread=new Thread(productor);
Thread thread1=new Thread(productor);
Thread thread2=new Thread(consumer);
Thread thread3=new Thread(consumer);
//啓動
thread.start();
// thread1.start();
thread2.start();
thread3.start();
new Thread(consumer).start();
}
}
運行程序,
消費者不能取出了,會等待,等待生產者往倉庫裏面放置產品。
生產者和消費者的問題特別重要,一定要掌握。
謝謝您的觀看,如果喜歡,請關注我,再次感謝 !!!