1. 程式人生 > >實戰Java高併發程式設計(五、並行模式與演算法)

實戰Java高併發程式設計(五、並行模式與演算法)

5.1單例模式

單例模式:是一種常用的軟體設計模式,在它的核心結構中值包含一個被稱為單例的特殊類。一個類只有一個例項,即一個類只有一個物件例項。

 對於系統中的某些類來說,只有一個例項很重要,例如,一個系統中可以存在多個列印任務,但是隻能有一個正在工作的任務;售票時,一共有100張票,可有有多個視窗同時售票,但需要保證不要超售(這裡的票數餘量就是單例,售票涉及到多執行緒)。如果不是用機制對視窗物件進行唯一化將彈出多個視窗,如果這些視窗顯示的都是相同的內容,重複建立就會浪費資源。

1.雙重檢查

public class Singleton {
    private static volatile Singleton singleton;
    private Singleton(){}

    public static Singleton getInstacne(){
        if (singleton == null){
            synchronized (Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

2.靜態內部類

public class Singleton {
    private Singleton(){}

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

5.2不變模式

不變模式的核心思想是一個物件一旦被建立,則它的內部狀態將永遠不會發生改變。 

比如一個物件的存活時間,它會隨時間的變化而變化,因此它是隻讀屬性,而不是不變屬性。

不變模式的主要應用場景:

  1. 當物件建立後,其內部狀態和資料不再發生變化
  2. 物件需要被共享,被多執行緒頻繁訪問
public final class Product {
    private final String no;
    private final String name;
    private final double price;

    public Product(String no,String name,double price){
        super();
        this.no = no;
        this.name = name;
        this.price = price;
    }

    public String getNo() {
        return no;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}

注意:不變模式通過迴避問題而不是解決問題的態度來處理 多執行緒併發訪問控制。不變物件是不需要進行同步控制的。由於併發同步會對效能產生不良的影響,因此,在需求允許的情況下,不變模式可以提高系統的併發效能和併發量。

5.3生產者消費者模式

在生產者與消費者之間建立一個緩衝區,對生產者執行緒和消費者執行緒進行解耦。

5.4Future模式

先想一個場景:假如你突然想做飯,但是沒有廚具,也沒有食材。網上購買廚具比較方便,食材去超市買更放心。

實現分析:在快遞員送廚具的期間,我們肯定不會閒著,可以去超市買食材。所以,在主執行緒裡面另起一個子執行緒去網購廚具。

public class test{
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 第一步 網購廚具
        Callable<Cooker> onlineShopping = new Callable<Cooker>() {
            @Override
            public Cooker call() throws Exception {
                System.out.println("第一步:下單");
                System.out.println("第一步:等待送貨");
                // 模擬送貨時間
                Thread.sleep(5000);
                System.out.println("第一步:快遞送到");
                return new Cooker();
            }

        };
        FutureTask<Cooker> task = new FutureTask<Cooker>(onlineShopping);
        new Thread(task).start();
        // 第二步 去超市購買食材
//        模擬購買食材時間
        Thread.sleep(2000);
        Food food = new Food();
        System.out.println("第二步:食材到位");
//        第三步 用廚具烹飪食材
//        聯絡快遞員,詢問是否到貨
        if (!task.isDone()) {
            System.out.println("第三步:廚具還沒到,心情好就等著(心情不好就呼叫cancel方法取消訂單)");
        }
        Cooker cooker = task.get();
        System.out.println("第三步:廚具到位,開始展現廚藝");
        cook(cooker, food);
        System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
    }

    /**
     * 廚具
     */
    static class Cooker{}

    /**
     * 食物
     */
    static class Food{}

    /**
     * 烹飪
     */
    static void cook(Cooker cooker,Food food){}
}

5.5並行搜尋

public class test {
    /**
     * 定義我們需要查詢的無序陣列
     */
    static int[] arr = {1, 22, 2, 3, 4, 5, 344, 6, 7, 8, 10, 9};
    /**
     * 定義執行緒池資料,已經存放結果的Result
     */
    static ExecutorService pool = Executors.newCachedThreadPool();
    static final int thread_num = 2;
    /**
     * 初始值定位-1
     */
    static AtomicInteger result = new AtomicInteger(-1);

    public static int search(int searchValue, int beginPos, int endPos) {
        for (int j = beginPos; j < endPos; j++) {
            if (result.get() >= 0) {
                return result.get();
            }
            if (arr[j] == searchValue) {
                //如果設定失敗,表示其他現場已經先找到了
                if (!result.compareAndSet(-1, j)) {
                    //如果當前值 == 預期值,則以原子方式將該值設定為給定的更新值。
                    //-1當前值為-1就返回
                    return result.get();
                }
                return j;
            }
        }
        return -1;
    }

    public static class SearchTask implements Callable<Integer> {
        int begin, end, searchValue;

        public SearchTask(int searchValue, int begin, int end) {
            this.begin = begin;
            this.end = end;
            this.searchValue = searchValue;
        }

        @Override
        public Integer call() {
            int re = search(searchValue, begin, end);
            return re;
        }
    }

    public static int psearch(int searchValue) throws InterruptedException, ExecutionException {
        int subArrSize = arr.length / thread_num + 1;
        List<Future<Integer>> re = new ArrayList<Future<Integer>>();
        for (int i = 0; i < arr.length; i += subArrSize) {
            int end = i + subArrSize;
            if (end >= arr.length) {
                end = arr.length;
            }
            re.add(pool.submit(new SearchTask(searchValue, i, end)));
        }
        for (Future<Integer> fu :
                re) {
            if (fu.get() >= 0) {
                return (fu.get());
            }

        }
        return -1;
    }

    public static void main(String[] args) {
        try {
            System.out.println(psearch(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

5.6NIO

NIO主要有三大核心部分:Channel(通道),Buffer(緩衝區), Selector。傳統IO基於位元組流和字元流進行操作,而NIO基於Channel和Buffer(緩衝區)進行操作,資料總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。Selector(選擇區)用於監聽多個通道的事件(比如:連線開啟,資料到達)。因此,單個執行緒可以監聽多個數據通道。

傳統io與nio區別:

public class test {
    public static void method1() {
        RandomAccessFile aFile = null;
        try {
            aFile = new RandomAccessFile("src/test.txt", "rw");
            FileChannel fileChannel = aFile.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);
            while (bytesRead != -1) {
                buf.flip();
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
                buf.compact();
                bytesRead = fileChannel.read(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (aFile != null) {
                    aFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void method2() {
        InputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream("src/test.txt"));
            byte[] buf = new byte[1024];
            int bytesRead = in.read(buf);
            while (bytesRead != -1) {
                for (int i = 0; i < bytesRead; i++) {
                    System.out.print((char) buf[i]);
                }
                bytesRead = in.read(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
//        method2();
        method1();
    }
}
  1. BIO方式適用於連線數目比較小且固定的架構,這種方式對伺服器資源要求比較高,併發侷限於應用中。比如:檔案的上傳下載
  2. NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜
  3. AIO方式使用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。