耦合&解耦
在談生產者消費者模型之前,我們先來談談耦合和解耦的概念,要是這兩個概念掌握了,那麼生產者消費者模型也就掌握一半啦~
耦合
耦合是指兩個或者兩個以上體系或兩種運動形式間通過相互左右而彼此影響以至聯合起來的現象。舉個例子,有一對熱戀中的情侶,水深火熱的,誰離開誰都不行了,離開就得死,要是對方有一點風吹草動,這一方就得地動山搖。可以按照瓊瑤阿姨的路子繼續想象,想成什麼樣都不過分,他們之間的這種狀態就應該叫做“耦合”。
解耦
解耦,字面意思就是解除耦合關係。一般的設計思想就是設計一個緩衝區,一方將自己的數據放在這個緩衝區,另一方可以從這個緩衝區獲取對方的數據,就是兩方不互相接觸,甚至可以一個都不知道一個的存在。舉個例子,還是上面的一對情侶,由於太過親密被他們父母知道了,所以她兩不能在一起了,但是他倆又不想斷了聯繫,所以他倆就想了個辦法,建了一個祕密基地,把想給對方說的話全放在這個祕密基地裏 ,所以看似他倆還在一起但是其實已經分開了,而且假如說有一天她兩的祕密基地被別人發現了,那個人也在裏面寫了話,她兩也不會發現。
wait()與notify()方法
wait()方法
- wait()的作用是使當前執行代碼的線程進行等待,wait()方法是Object類的方法,該方法是用來將當前線程置入“預執行隊列”中,並且在wait()所在的代碼處停止執行,直到接到通知或被中斷爲止。
- wait()方法只能在同步方法中或同步塊中調用。如果調用wait()時,沒有持有適當的鎖,會拋出異常。
- wait()方法執行後,當前線程釋放鎖,線程與其它線程競爭重新獲取鎖。
範例:wait()方法的使用
public static void main(String[] args) {
Object obj=new Object();
synchronized (obj){
System.out.println("執行同步塊...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行同步塊結束...");
}
}
這樣在執行到obj.wait()之後就一直等待下去,那麼程序肯定不能一直這麼等待下去了。這個時候就需要使用到了另外一個方法喚醒的方法notify()。
notify()方法
notify方法就是使停止的線程繼續運行。
- 方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,並使它們重新獲取該對象的對象鎖。如果有多個線程等待,則有線程規劃器隨機挑選出一個呈wait狀態的線程。
- 在notify()方法後,當前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出同步代碼塊之後纔會釋放對象鎖。
範例:notify()
public class MyThread1 {
public static void main(String[] args) {
Object obj=new Object();
Thread threadA=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"執行同步塊..");
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"執行同步塊結束..");
});
threadA.setName("Thread-A");
threadA.start();
Thread threadB=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"執行同步塊..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
obj.notify();
}
System.out.println(Thread.currentThread().getName()+"執行同步塊結束..");
});
threadB.setName("Thread-B");
threadB.start();
}
}
notifyAll()
public class MyThread1 {
public static void main(String[] args) {
Object object=new Object();
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"代碼段開始..");
synchronized (object){
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"代碼段結束..");
}
};
for(int i=0;i<3;i++){
Thread thread=new Thread(runnable);
thread.setName("Thread-Wait"+i);
thread.start();
}
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object){
System.out.println("Thread-notify已經運行中..");
object.notifyAll();
}
}
});
thread.setName("Thread-notify");
thread.start();
}
}
出現阻塞的情況
- 線程調用 sleep方法,主動放棄佔用的處理器資源。
- 線程調用了阻塞式IO方法,在該方法返回前,該線程被塞。
- 線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。
- 線程等待某個通知。
- 程序調用了 suspend方法將該線程掛起。此方法容易導致死鎖,儘量避免使用該方法。
run()方法運行結束後進入銷燬階段,整個線程執行完畢。每個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列。就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。一個線程被喚醒後,纔會進入就緒隊列,等待CPU的調度;反之,一個線程被wait後,就會進入阻塞隊列,等待下一次被喚醒。
生產者消費者模型
生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產者生產完數據之後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。
這個阻塞隊列就是用來給生產者和消費者解耦的。縱觀大多數設計模式,都會找一個第三者出來進行解耦,如工廠模式的第三者是工廠類,模板模式的第三者是模板類。在學習一些設計模式的過程中,如果先找到這個模式的第三者,能幫助我們快速熟悉一個設計模式。
生產者消費者模型要解決的問題:
1.生產和消費速度不匹配
2.軟件開發組件的解耦
範例:
package hhh.Test.MyThread;
public class Goods {
private final String id;
private final String name;
public Goods(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Goods{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
package hhh.Test.MyThread;
public class PCConfig {
//可以通過這些值來調整隊列大小和速率
public static final int MAX_CAPACITY=10;
public static final int MAX_PRODUCER=5;
public static final int MAX_CUSTOMER=4;
}
package hhh.Test.MyThread;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
//靜態導入
import static hhh.Test.MyThread.PCConfig.MAX_CAPACITY;
import static hhh.Test.MyThread.PCConfig.MAX_CUSTOMER;
import static hhh.Test.MyThread.PCConfig.MAX_PRODUCER;
public class TestPC {
public static void main(String[] args) {
final Queue queue=new ArrayBlockingQueue(MAX_CAPACITY);//設置隊列最大容量爲10
//創建生產者線程
final Runnable producer=new Producer(queue);
for(int i=0;i<MAX_PRODUCER;i++) {
Thread pThread = new Thread(producer, "Thread-Producer");
pThread.start();
}
//創建消費者xianc
final Runnable custorm=new Customer(queue);
for(int i=0;i<MAX_CUSTOMER;i++) {
Thread cThread = new Thread(custorm, "Thread-Custorm");
cThread.start();
}
}
}
package hhh.Test.MyThread;
import java.util.Queue;
public class Customer implements Runnable {
private final Queue<Goods> queue;
public Customer(Queue<Goods> queue) {
this.queue = queue;
}
public void run(){
while(true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (queue) {
if (queue.isEmpty()) {//如果爲空,就喚醒所有生產者線程生產商品
queue.notifyAll();
} else {
Goods goods = queue.poll();
System.out.println(Thread.currentThread().getName() + "消費 " + goods);
}
}
}
}
}
package hhh.Test.MyThread;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
//靜態導入
import static hhh.Test.MyThread.PCConfig.MAX_CAPACITY;
import static hhh.Test.MyThread.PCConfig.MAX_CUSTOMER;
import static hhh.Test.MyThread.PCConfig.MAX_PRODUCER;
public class TestPC {
public static void main(String[] args) {
final Queue queue=new ArrayBlockingQueue(MAX_CAPACITY);//設置隊列最大容量爲10
//創建生產者線程
final Runnable producer=new Producer(queue);
for(int i=0;i<MAX_PRODUCER;i++) {
Thread pThread = new Thread(producer, "Thread-Producer");
pThread.start();
}
//創建消費者xianc
final Runnable custorm=new Customer(queue);
for(int i=0;i<MAX_CUSTOMER;i++) {
Thread cThread = new Thread(custorm, "Thread-Custorm");
cThread.start();
}
}
}