實戰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不變模式
不變模式的核心思想是一個物件一旦被建立,則它的內部狀態將永遠不會發生改變。
比如一個物件的存活時間,它會隨時間的變化而變化,因此它是隻讀屬性,而不是不變屬性。
不變模式的主要應用場景:
- 當物件建立後,其內部狀態和資料不再發生變化
- 物件需要被共享,被多執行緒頻繁訪問
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();
}
}
- BIO方式適用於連線數目比較小且固定的架構,這種方式對伺服器資源要求比較高,併發侷限於應用中。比如:檔案的上傳下載
- NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜
- AIO方式使用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。