Java併發程式設計中的設計模式解析(一)

leoliu168發表於2018-11-06

Java併發程式設計,除了被用於各種Web應用、分散式系統和大資料系統,構成高併發系統的核心基礎外,其本身也蘊含著大量的設計模式思想在裡面。這一系列文章主要是結合Java原始碼,對併發程式設計中使用到的、實現的各類設計模式做歸納總結,以便進一步沉澱對Java併發設計的理解。

模板設計模式

Thread類中run和start方法,就是一個典型的模板設計模式的實現,即:父類定義演算法邏輯程式碼,子類實現其細節

 1 public synchronized void start() {
 2         /**
 3          * 執行緒物件新建後的New狀態,其內部thereadStatus屬性為0
 4          */
 5         if (threadStatus != 0)
 6             throw new IllegalThreadStateException();
 7 
 8         /* 同時會被新增到一個ThreadGroup */
 9         group.add(this);
10 
11         boolean started = false;
12         //呼叫JNI方法start0()來啟動執行緒
13         try {
14             start0();
15             started = true;
16         } finally {
17         //執行緒結束之後,再次啟動將丟擲異常
18             try {
19                 if (!started) {
20                     group.threadStartFailed(this);
21                 }
22             } catch (Throwable ignore) {
23                 /* do nothing. If start0 threw a Throwable then
24                   it will be passed up the call stack */
25             }
26         }
27     }

下面以一個例子演示模板模式:

public class TemplateMethod {
    //相當於Thread類的start方法, 用final修飾避免被更改
    public final void print(String message) {
        System.out.println("-------------");
        wrapPrint(message);
        System.out.println("-------------");
    }
    //相當於Thread的run方法, 用protected修飾限於子類重寫
    protected void wrapPrint(String message) {
        
    }
    
    public static void main(String[] args) {
        //通過匿名內部子類, 重寫父類的wrapPrint方法, 從而實現不同的輸出模板
        TemplateMethod t1 = new TemplateMethod() {
            @Override
            protected void wrapPrint(String message) {
                System.out.println("111" + message + "111");
            }
        };
        t1.print("Hello World!");
        
        TemplateMethod t2 = new TemplateMethod() {
            @Override
            protected void wrapPrint(String message) {
                System.out.println("222" + message + "222");
            }
        };
        t2.print("Hello World!");
    }
}

策略模式

建立Java多執行緒中,實現Runnable介面作為Target並傳入Thread類的構造方法來生成執行緒物件的過程,就體現了GoF中策略模式的設計思想。下面是一個簡單的示例:

首先,仿照Runnable介面的思想,定義一個用於處理資料庫行的介面

1 /*
2  * RowHandler定義了對資料庫查詢返回結果操作的方法, 具體實現需要
3  * 實現類完成, 類似於Runnable介面
4  */
5 public interface RowHandler<T> {
6     T handle(ResultSet rs);
7 }

然後,仿照Thread方法,定義資料庫查詢的工作類

 1 public class RecordQuery  {
 2     
 3     private final Connection connection;
 4 
 5     public RecordQuery(Connection connection) {
 6         this.connection = connection;
 7     }
 8     //方法中傳入RowHandler的實現類
 9     public <T> T query(RowHandler<T> handler, String sql, Object... params) throws SQLException {
10             PreparedStatement stmt;
11             ResultSet resultSet;
12             stmt = connection.prepareStatement(sql);
13             int index = 1;
14             for (Object param : params) {
15                 stmt.setObject(index++, param);
16             }
17             resultSet = stmt.executeQuery();
18             //呼叫實現類的handle方法來處理資料
19             return handler.handle(resultSet);
20     }
21 }

生產者-消費者模式

生產者-消費者模式是使用Java併發程式設計通訊所實現的經典模式之一。該模式是通過佇列這一資料結構來儲存物件元素,由多執行緒分別充當生產者和消費者,生產者不斷生成元素、消費者不斷消費元素的過程。下面通過程式碼來演示:

實現一個帶有入隊和出隊的佇列

 1 /*
 2  * 通過一個生產者-消費者佇列來說明執行緒通訊的基本使用方法
 3  */
 4 public class EventQueue {
 5     //定義一個佇列元素數量, 一旦賦值則不可更改
 6     private final int max;
 7     //定義一個空的內部類, 代表儲存元素
 8     static class Event{        }
 9     //定義一個不可改的連結串列集合, 作為佇列載體
10     private final LinkedList<Event> eventQueue = new LinkedList<>();
11     //如果不指定初始容量, 則容量預設為10
12     private final static int DEFAULT_MAX_EVENT = 10;
13     //使用自定義容量初始化佇列
14     public EventQueue(int max) {
15         this.max = max;
16     }
17     //如果不指定初始容量, 則容量預設為10
18     public EventQueue() {
19         this(DEFAULT_MAX_EVENT);
20     }
21     //封裝一個輸出到控制檯的方法
22     private void console(String message) {
23         System.out.printf("%s:%s
",Thread.currentThread().getName(), message);
24     }
25     //定義入隊方法
26     public void offer(Event event) {
27         //使用連結串列物件作為鎖, 通過synchronized程式碼塊實現同步
28         synchronized(eventQueue) {
29             //在迴圈中判斷如果佇列已滿, 則呼叫鎖的wait方法, 使生產者執行緒阻塞
30             while(eventQueue.size() >= max) {
31                 try {
32                     console(" the queue is full");
33                     eventQueue.wait();
34                 } catch (InterruptedException e) {
35                     e.printStackTrace();
36                 }
37             }
38             console(" the new event is submitted");
39             eventQueue.addLast(event);
40             //喚醒所有等待中的消費者;注意如果此處使用notify(),可能導致執行緒不安全
41             this.eventQueue.notifyAll();
42         }
43     }
44     //定義出隊方法
45     public Event take() {
46         //使用連結串列物件作為鎖
47         synchronized(eventQueue) {
48             //在迴圈中判斷如果佇列已空, 則呼叫鎖的wait方法, 使消費者執行緒阻塞
49             while(eventQueue.isEmpty()) {
50                 try {
51                     console(" the queue is empty.");
52                     eventQueue.wait();
53                 } catch (InterruptedException e) {
54                     e.printStackTrace();
55                 }
56             }
57             Event event = eventQueue.removeFirst();
58             //喚醒所有等待中的生產者;注意如果此處使用notify(),可能導致執行緒不安全
59             this.eventQueue.notifyAll();
60             console(" the event " + event + " is handled/taked.");
61             return event;
62         }
63     }
64 }

驗證該佇列的類

 1 /*
 2  * producer/client pattern
 3  */
 4 public class EventClient {
 5     
 6     public static void main(String[] args) {
 7         //定義不可變佇列例項
 8         final EventQueue eventQueue = new EventQueue();
 9         //新建生產者執行緒, 可以設定多個
10         new Thread(()->{
11             while(true) {
12                 eventQueue.offer(new EventQueue.Event());
13             }
14         }, "producer").start();
15         //新建消費者執行緒, 可以設定多個
16         new Thread(()->{
17             while(true) {
18                 eventQueue.take();
19                 try {
20                     TimeUnit.MILLISECONDS.sleep(10);
21                 } catch (InterruptedException e) {
22                     e.printStackTrace();
23                 }
24             }
25         }, "consumer").start();
26     }
27 }


相關文章