1. 程式人生 > >實習筆記(二)設計模式和java工具

實習筆記(二)設計模式和java工具

一 設計模式

下面舉幾個常用設計模式:單例模式;工廠模式(抽象工廠模式);觀察者模式;建造者模式;代理模式

有20多種設計模式,最基礎的是上面這幾種,很多其實使他們的變形和擴充套件。

綜述:我認為,設計模式就是在程式碼骨架,目的是讓自己的程式碼高內聚低耦合,可讀性強,可複用性強,可能一開始構思會慢,先寫很多借口或與功能無關的程式碼,但是卻能在日後數倍的減少程式碼量和維護程式碼產生工作量。一般我在開發中如果有冗餘程式碼,或者多個檔案中有雷同的程式碼,就是沒用好設計模式。程式碼中沒有冗餘程式碼,整個工程中沒有相同邏輯和功能的程式碼,而且高內聚低耦合,使他人容易閱讀和擴充套件日後的需求,而且可移植可複用,就是好的設計模式。
介面的定義與實現分離,將邏輯層抽象出來,物件的動態例項化(也就是除了引數的傳遞,將物件功能的實現放在最後)

  1. 單例模式
    單例模式是最簡單模式,就是一個類在記憶體中只有一個例項。
    在類內例項化,並定義為私有。
public class  Singleton implements Runnable{
    public static int num = 0;
    private static final Singleton singleton = new Singleton();

    public Singleton(){
        num++;
    };
    public static Singleton getSingleton(){
        return singleton;
    }
    @Override
    public void run(){
        System.out.println(num);
    }
}

 @Test
    public  void testSingleTon(){
        for(int i =0; i<100;i++) {
            Thread thread = new Thread(Singleton.getSingleton());
            thread.run();
        }
    }
    
    #上面程式碼輸出是100個1,因為該類只有一個例項化的物件,只例項化一次,所有類靜態變數num一直是1
    
    #如果我改成如下程式碼 
    public class  Singleton implements Runnable{
    public static int num = 0;
    public Singleton(){
        num++;
    };
    public static Singleton getSingleton(){
        return new Singleton();
    }
    @Override
    public void run(){
        System.out.println(num);
    }
}
#單元測試輸出1-100

因為多執行緒每個執行緒都有一次例項化,這就是多例模式和單例模式的區別。單例只允許記憶體中存在一個例項化的物件,一直駐留記憶體。單例模式可在系統設定全域性訪問點,優化和共享資源訪問。

主義:單例模式必須是執行緒安全的,比如全域性唯一序列。
要保證執行緒執行緒同步,高併發下記憶體中可能出現多例項。所有要加互斥鎖。使用執行緒安全容器Vector替代ArrayList,在方法前加同步互斥鎖synchronized
  1. 工廠模式
    就是建立一個例項化不同子類的介面,將一個類的例項化延遲到其子類。工廠模式通常對應著抽象類,將工廠定義為抽象類,裡面寫好所有子類的通用方法,讓不同的子類繼承。但是並不常用,每個子類都要繼承,子類較多也很麻煩,而且如果工廠需要修改,會波及所有子類。
    抽象工廠模式,解決了只要給出需要的產品,給工廠就能生產,無需知道具體細節,每個產品有各自的實現類,橫向擴充套件容易,可以無限量種產品,但是抽象類一旦定義,修改很難,也就是約束很難修改。

  2. 觀察者模式
    也叫(Publish/subscribe)釋出訂閱模式
    定義物件之間一對多的依賴關係,使得當一個物件改變,所有依賴它的物件都得到通知並被自動更新
    被觀察者定義一個執行緒安全陣列,存放它的觀察者,並能增刪觀察者,併發送訊息。

注意:如果是順序向觀察者傳送訊息,一個觀察者阻塞會影響整個執行效率,要用非同步方式(需要考慮執行緒安全和佇列問題,一定要懂Message Queue訊息佇列)。
  1. 建造者模式

當使用的工具相同,相同方法,不同的執行順序,會產生不同結果時,考慮採用建造者模式。需要在建造者類中用有序集合存放每個實現類的執行序列,將每個產品的序列和引數傳遞給Builder,並有它呼叫不同產品的實現類,使用者只需要告訴Builder序列和名稱,無需知道具體實現。跟工廠模式類似,但是關注點在序列,而非實現的封裝。

  1. 代理模式

代理模式是java中最常用的,面向切面的程式設計,即AOP就是代理模式,這也是最難理解的模式。代理,就想到遊戲帶練,不光如此。把我想執行的一個操作交給定義的代理服務,他會有berfore和after,即該操作前後的一些必要工作。代理類與委託類有同樣的介面,代理類負責預處理訊息、過濾、許可權驗證、日誌記錄等必要操作。
有靜態代理和動態代理,靜態代理每個代理類只為一個介面服務,多代理肯定會有重複程式碼,動態代理是程式執行時,由反射機制動態建立。ava.lang.reflect 包中的Proxy類和InvocationHandler 介面提供了生成動態代理類的能力。

二 工具

1. 非同步執行緒池

這篇部落格很詳細
https://blog.csdn.net/tuke_tuke/article/details/51353925?utm_source=blogxgwz0
Java通過Executors提供四種執行緒池,分別為:
newCachedThreadPool建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
newFixedThreadPool 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
newScheduledThreadPool 建立一個定長執行緒池,支援定時及週期性任務執行。
newSingleThreadExecutor 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
submit和execute
1、接收的引數不一樣 submit()可以接受runnable無返回值和callable有返回值
execute()接受runnable 無返回值

2、submit有返回值,而execute沒有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

用到返回值的例子,比如說我有很多個做validation的task,我希望所有的task執行完,然後每個task告訴我它的執行結果,是成功還是失敗,如果是失敗,原因是什麼。

3、submit方便Exception處理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task裡會丟擲checked或者unchecked exception,而你又希望外面的呼叫者能夠感知這些exception並做出及時的處理,那麼就需要用到submit,通過捕獲Future.get丟擲的異常。

ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue<Runnable> workQueue) 
corePoolSize: 核心執行緒數,預設情況下核心執行緒會一直存活,即使處於閒置狀態也不會受存keepAliveTime限制。除非將allowCoreThreadTimeOut設定為true。

maximumPoolSize:執行緒池所能容納的最大執行緒數。超過這個數的執行緒將被阻塞。當任務佇列為沒有設定大小的LinkedBlockingDeque時,這個值無效。

keepAliveTime:非核心執行緒的閒置超時時間,超過這個時間就會被回收。

unit:指定keepAliveTime的單位,如TimeUnit.SECONDS。當將allowCoreThreadTimeOut設定為true時對corePoolSize生效。

workQueue:執行緒池中的任務佇列.常用的有三種佇列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue
import java.util.concurrent.*;
public class ExecutorServiceTest {
    ExecutorService threadPool =  new ThreadPoolExecutor(
            1, 4,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024)
    );

    public void asyncHandle(LimitQueue<Integer> limitQueue){
        threadPool.execute(()->{
            System.out.println("consumer"+limitQueue.poll());
        });
    }
}

public class Producer implements Runnable {

    public Producer(LimitQueue<Integer> limit){
        Singleton.getSingleton();
        limit.offer(Singleton.getNum());
    }

    @Override
    public void run(){
        System.out.println("produce"+ Singleton.getNum());
    }
}

public class Consumer implements Runnable{

   private LimitQueue<Integer> limitQueue;
   
    public Consumer(LimitQueue<Integer> limit){
       this.limitQueue = limit;
    }

    @Override
    public void run(){
        ExecutorServiceTest executorServiceTest = new ExecutorServiceTest();
        executorServiceTest.asyncHandle(limitQueue);
    }
}

public class TestProducerConsumer{
    private LimitQueue<Integer> limitQueue = new LimitQueue<>(10);
    public void start(){
        Producer producer = new  Producer(limitQueue);
        Thread t = new Thread(producer);
        t.start();

        Consumer consumer = new Consumer(limitQueue);
        Thread t2 = new Thread(consumer);
        t2.start();
    }

    public static void main(String args[]){
        int i = 0;
       while (i<100) {
           i++;
           TestProducerConsumer testProducerConsumer = new TestProducerConsumer();
           testProducerConsumer.start();
       }
    }
}

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
public class LimitQueue<E> implements Serializable {

    /**
     * 佇列長度
     */
    private int limit;

    @Getter
    private Queue<E> queue;

    public LimitQueue(int limit) {
        this.limit = limit;
        this.queue = new LinkedBlockingQueue<>(limit);
    }

    /**
     * 入列:當佇列大小已滿時,把隊頭的元素poll掉
     */
    @JSONField(serialize = false)
    public void offer(E e) {
        if (null == e) {
            return;
        }
        if (queue.size() >= limit) {
            log.debug("offer poll size = {} limit = {}", queue.size(), limit);
            queue.poll();
        }
        queue.offer(e);
    }

    @JSONField(serialize = false)
    public E poll() {
        return queue.poll();
    }

    @JSONField(serialize = false)
    public int getLimit() {
        return limit;
    }

    @JSONField(serialize = false)
    public int size() {
        return queue.size();
    }

    @JSONField(serialize = false)
    public List<E> toList() {
        return new ArrayList<>(queue);
    }
}


上面演示了一個簡單的生產者消費者模式,生產者和消費者都是實現了Runnable介面,Thread類的start方法會執行多執行緒,上面消費者使用非同步執行緒池跑,可以看到producer不是順序讀取佇列的。
如果消費者不使用ThreadPoolExecutor,那麼會看到生產者和消費者的序號是對應的。

#去掉非同步執行緒池,先進先出,有序
produce1
produce2
produce3
produce4
consumer1
consumer3
consumer2
consumer4
produce5
consumer5
produce6
consumer6
produce7
......
#消費者使用非同步執行緒池
produce34
produce35
produce36
consumer19
consumer5
consumer22
consumer2
consumer25
consumer35
consumer11
consumer23
consumer27
consumer7
consumer14
consumer16
consumer29
consumer32
consumer10

上面的例子是在實習是遇到的,需求是讀取視訊流,呼叫檢測跟蹤演算法,視訊流是實時的,不是TOC的網站介面與使用者互動。所有面對一秒多幀的視訊流,將解碼器物件封裝成生產者,消費者呼叫檢測演算法,流處理都要做成多執行緒,一幀出錯不至於影響後續,非同步工具用在將處理後的資料傳送到伺服器端。在RPC呼叫的部分使用非同步執行緒池,就是保證消費者在資料後處理時,不會因為RPC延遲影響資料的處理,如果是序列處理,很容易出問題,執行緒池開的較大,未處理完成的也會放到阻塞佇列,執行緒生存時間也會及時殺死無效程序。