1. 程式人生 > >五、生產者消費者模型

五、生產者消費者模型

增強 阻塞 介紹 並發 定義 ron pan exc rgs

1、生產者消費者模型作用和示例如下:
1)通過平衡生產者的生產能力和消費者的消費能力來提升整個系統的運行效率 ,這是生產者消費者模型最重要的作用
2)解耦,這是生產者消費者模型附帶的作用,解耦意味著生產者和消費者之間的聯系少,聯系越少越可以獨自發展而不需要收到相互的制約
備註:對於生產者消費者模型的理解將在並發隊列BlockingQueue章節進行說明,本章不做詳細介紹。

技術分享圖片
package threadLearning.productCustomerModel;
/*
    wait/notify 機制:以資源為例,生產者生產一個資源,通知消費者就消費掉一個資源,生產者繼續生產資源,消費者消費資源,以此循環。
    wait():使一個線程處於等待(阻塞)狀態,並且釋放所持有的對象的鎖;
    sleep(): 使一個正在運行的線程處於睡眠狀態, 是一個靜態方法, 調用此方法要處理 InterruptedException 異常;
    notify():喚醒一個處於等待狀態的線程,當然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程而是由 JVM 確定喚醒哪個線程,而且與優先級無關;
    notityAll():喚醒所有處於等待狀態的線程,該方法並不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態;
備註:java 5 通過 Lock 接口提供了顯示的鎖機制,Lock 接口中定義了加鎖(lock()方法)和解鎖(unLock()方法),增強了多線程編程的靈活性及對線程的協調
*/ //資源對象:包含商品名屬性;提供生產和消費方法; class Resource { private String name;//商品名 private int count = 0; private boolean flag = false;//生產或者消費的控制開關 public synchronized void set(String name) { // 生產資源 while (flag) { try { // 線程等待。消費者消費資源 wait(); }
catch (Exception e) { } } this.name = name + "---" + count++; System.out.println(Thread.currentThread().getName() + "...生產者..." + this.name); flag = true; // 喚醒等待中的消費者 this.notifyAll();//喚醒在此對象監視器上等待的所有線程 Object.notifyAll() }
public synchronized void out() { // 消費資源 while (!flag) { // 線程等待,生產者生產資源 try { wait(); } catch (Exception e) { } } System.out.println(Thread.currentThread().getName() + "...消費者..." + this.name); flag = false; // 喚醒生產者,生產資源 this.notifyAll(); } } // 生產者 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } // 生產者生產資源 public void run() { while (true) { res.set("商品"); } } } // 消費者消費資源 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while (true) { res.out(); } } } public class ProducerConsumerDemo { public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); t1.start(); t2.start(); } }
View Code

2、ThreadLocal
  ThreadLocal提供一個線程的局部變量,訪問某個線程擁有自己局部變量。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
ThreadLocal的接口方法只有4個方法,先來了解一下:
?void set(Object value)設置當前線程的線程局部變量的值;
?public Object get()該方法返回當前線程所對應的線程局部變量;
?public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度;
?protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的缺省實現直接返回一個null;
總的來說ThreadLocal就是一種以 空間換時間 的做法,在每個Thread裏面維護了一個以開地址法實現的ThreadLocal.ThreadLocalMap,把數據進行隔離,數據不共享,自然就沒有線程安全方面的問題了。
示例1

技術分享圖片
package threadLearning.thredLocal;
/*
1、該類提供了線程局部 (thread-local) 變量。這些變量不同於它們的普通對應物,因為訪問某個變量(通過其 get 或 set 方法)
的每個線程都有自己的局部變量,它獨立於變量的初始化副本。ThreadLocal 實例通常是類中的 private static 字段,
它們希望將狀態與某一個線程(例如:用戶 ID 或事務 ID)相關聯。
2、ThreadLocal的使用
(1) 在關聯數據類中創建 private static ThreadLocal
在下面的類中,私有靜態 ThreadLocal 實例(serialNum)為調用該類的靜態 SerialNum.get() 方法的每個
線程維護了一個“序列號”,該方法將返回當前線程的序列號。(線程的序列號是在第一次調用 SerialNum.get() 時
分配的,並在後續調用中不會更改。 
每個線程都保持對其線程局部變量副本的隱式引用,只要線程是活動的並且 ThreadLocal 實例是可訪問的;在線程消失之後,
其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。 

*/ 
public class SerialNum {
    private static int nextSerialNum = 3;
    private static ThreadLocal serialNum = new ThreadLocal() {//創建一個線程本地變量
        protected synchronized Object initialValue() {
            return new Integer(nextSerialNum++);
        }
    };

    public static int get() {
        return ((Integer) (serialNum.get())).intValue();
    }
    
    public static void main(String args[]){
        Thread thead1=new Thread(new Runnable() {
            public void run() {
                System.out.println("thead1-->"+get());                
            }
        });
        Thread thead2=new Thread(new Runnable() {
            public void run() {
                System.out.println("thead2-->"+get());                
            }
        });
        thead1.start();
        thead2.start();
        /*
    同一個Thread啟動第二次會報錯java.lang.IllegalThreadStateExceptionThread報錯的原因,並不是說,重新啟動Thread導致的,
而是因為共用一個Thread導致的,因為,如果是實現Runnable的類,每次啟動線程都需要new Thread(Runnable).start(),這就使得線
程沒有被共用。
        while(true){
            thead2.start();
            
        }
        */
    }
}
View Code

示例2

技術分享圖片
package threadLearning.thredLocal;
class Res {
    // 生成序列號共享變量
    public static Integer count = 0;
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        // 覆蓋返回此線程局部變量的當前線程的“初始值”方法
        @Override
        protected Integer initialValue() {
            return 0;
        };
    };

    public Integer getNum() {
        int count = threadLocal.get() + 1;//get() 該方法返回當前線程所對應的線程局部變量
        threadLocal.set(count);//將此線程局部變量的當前線程副本中的值設置為指定值
        return count;
    }
}

public class ThreadLocaDemo2 extends Thread {
    private Res res;

    public ThreadLocaDemo2(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
        }

    }

    public static void main(String[] args) {
        Res res = new Res();
        ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
                      //各個線程都具有線程局部變量threadLocal值,不會出現線程安全問題
        threadLocaDemo1.start();
        threadLocaDemo2.start();
        threadLocaDemo3.start();
    }

}
View Code

五、生產者消費者模型