第一章、The power and perils of concurrency(併發編程的威力和風險)java
1.1 Threads: The Flow of Execution(線程:程序執行流程)編程
線程能夠當作是進程中的一個執行流程。當運行一個程序時,進 程中至少有一個線程在執行。咱們能夠經過建立線程的方式來啓動其餘的執行流程以併發的處理任務。後端
1.2 The Power of Concurrency(併發的威力)跨域
咱們對併發感興趣的緣由有兩點:併發編程能夠提升應用的響應速度和提高用戶體驗。緩存
須要使用併發的幾種場景:Services(服務程序)、computationally intensive applications(計算密集型應用程序)、datacrunching applications(數據分析處理應用程序)。安全
總之併發能夠幫助咱們提高應用的響應(responsive),下降延遲(latency),提升系統的吞吐量(throughput
)。服務器
1.3 The Perils of Concurrency(併發的風險)併發
併發的3大問題:飢餓(starvation),死鎖(deadlock),競爭條件(race condition)。前兩個問題比較容易檢測甚至避免,可是競爭條件是一個很棘手的問題。app
1.3.1 瞭解內存柵欄(memory barrier)jvm
內存柵欄就是從本地或者功能內存到主存的拷貝動做,僅當寫線程先跨域內存柵欄,讀操做後跨域內存柵欄時,寫操做的變動纔會對其餘線程可見,sychronized和volatile都強制規定了全部的變動必須全局所見。在程序運行中全部的變動會先在寄存器(registers)或者緩存中完成,而後在拷貝到主存中以跨域內存柵欄。
1.3.2 Avoid Shared Mutability(避免共享可變)
1.4 Recap(小結)
不管是在開發一個客戶端桌面交互程序,仍是一個高性能的後端服務器應用。併發編程將扮演着一個很重要的角色。併發編程能夠幫助咱們提升應用的響應,提高用戶體驗以及充分利用現代的多核處理器性能。
傳統基於可變性處理的jvm併發編程模型帶來了不少的問題。建立完線程後,咱們須要努力的避免程序陷入飢餓,死鎖和競爭條件下。經過消除共享可變性,咱們能夠從根本上去解決上述問題。而不可變性的編程將會是併發變得簡單、安全、有趣。
第二章、Division of Labor (分工)
2.1 From sequential to concurrency(從順序到併發)
Divide and Conquer(分而治之)
Determining the Number of Threads(肯定線程數量)
獲取系統的可用處理器核心數,經過下面代碼很容易獲得:
Runtime.getRuntime().availableProcessors();
所以線程的最小數量應該等於系統可用核心數的數量。對於計算密集型任務,建立和核心數相同的線程數就能夠了,更多的線程數反而會致使性能降低,由於在多個任務處於就緒狀態時,處理器核心須要在線程間不停的切換上下文。這種切換對程序性能損耗較大。可是若是任務都是IO密集型就須要開更多的線程。
當一個任務執行IO操做時,線程將會被阻塞,處理器當即會執行上下文切換以便處理其餘就緒線程,若是咱們只有核心數那麼個線程,即便有待執行的任務也沒法處理。由於拿不出更多的線程來供給處理器調度。
若是任務的50%的時間處於阻塞狀態,則將線程數設置爲處理器核心數的2倍,若是任務阻塞等待的少於50%,即這些任務屬於計算密集型的,則程序所需線程數隨之減小,但最少也不能低於處理器核心數。若是任務的阻塞時間大於執行時間,則該任務屬於IO密集型。這種須要增長線程數。
所以能夠計算出所需的線程數:
線程數 = 可用核心數/(1-阻塞係數)
計算密集型的阻塞係數爲0,而IO密集型的阻塞係數接近1,
Determining the Number of Parts(肯定任務數)
前面提到了如何計算所需線程數,如今須要肯定如何分解問題。換句話說就是把一個大的任務分解成幾個小的任務。
2.2 在IO密集型應用中使用併發
對於IO密集型應用阻塞都很大,所需的線程數通常都大於可用核心數。
數字例子主要是講解一個從Yahoo獲取前一天的股票收盤價,計算總n只股的總淨值。
併發源碼:
/** * 獲取先一個交易日的收盤價 * */ public class YahooFinance { public static double getPrice(final String ticker) throws IOException { final URL url = new URL("http://ichart.finance.yahoo.com/table.csv?s=" + ticker); final BufferedReader reader = new BufferedReader( new InputStreamReader(url.openStream())); //Date,Open,High,Low,Close,Volume,Adj Close //2011-03-17,336.83,339.61,330.66,334.64,23519400,334.64 final String discardHeader = reader.readLine(); final String data = reader.readLine(); final String[] dataItems = data.split(","); final double priceIsTheLastValue = Double.valueOf(dataItems[dataItems.length - 1]); return priceIsTheLastValue; } }
股票信息抽象處理類
public abstract class AbstractNAV { //從文件中讀取每隻股的股票信息 public static Map<String, Integer> readTickers() throws IOException { final InputStream io =Thread.currentThread().getContextClassLoader() .getResourceAsStream("stocks.txt"); final BufferedReader reader = new BufferedReader(new InputStreamReader(io)); final Map<String, Integer> stocks = new HashMap<>(); String stockInfo = null; while ((stockInfo = reader.readLine()) != null) { final String[] stockInfoData = stockInfo.split(","); final String stockTicker = stockInfoData[0]; final Integer quantity = Integer.valueOf(stockInfoData[1]); stocks.put(stockTicker, quantity); } return stocks; } //累加用戶所持股票的價格 public void timeAndComputeValue() throws ExecutionException, InterruptedException, IOException { final long start = System.nanoTime(); final Map<String, Integer> stocks = readTickers(); final double nav = computeNetAssetValue(stocks); final long end = System.nanoTime(); final String value = new DecimalFormat("$##,##0.00").format(nav); System.out.println("Your net asset value is " + value); System.out.println("Time (seconds) taken " + (end - start)/1.0e9); } public abstract double computeNetAssetValue( final Map<String, Integer> stocks) throws ExecutionException, InterruptedException, IOException; }
併發處理
public class ConcurrentNAV extends AbstractNAV { public double computeNetAssetValue(final Map<String, Integer> stocks) throws InterruptedException, ExecutionException { final int numberOfCores = Runtime.getRuntime().availableProcessors(); final double blockingCoefficient = 0.9; final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient)); System.out.println("Number of Cores available is " + numberOfCores); System.out.println("Pool size is " + poolSize); final List<Callable<Double>> partitions = new ArrayList<>(); for(final String ticker : stocks.keySet()) { partitions.add(new Callable<Double>() { public Double call() throws Exception { return stocks.get(ticker) * YahooFinance.getPrice(ticker); } }); } System.out.println("tasks "+ partitions.size()); final ExecutorService executorPool = Executors.newFixedThreadPool(poolSize); final List<Future<Double>> valueOfStocks = executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS); double netAssetValue = 0.0; for(final Future<Double> valueOfAStock : valueOfStocks) netAssetValue += valueOfAStock.get(); executorPool.shutdown(); return netAssetValue; } public static void main(final String[] args) throws ExecutionException, InterruptedException, IOException { new ConcurrentNAV().timeAndComputeValue(); } }
2.3 在計算密集型應用中使用併發
書中例子主要講述使用併發查找素數,例子代碼略......
2.4 Strategies for Effective Concurrency(有效的併發策略)
咱們應該儘量的提供共享不可變性,不然就應該遵照隔離可變性原則,即保證老是隻有一個線程能夠訪問可變變量。
第三章 Design Approaches(設計方法)
這一章主要探究無需改變內存中任何數據的設計方法。
3.1 Dealing with State(處理狀態)
雖然處理狀態是不可避免的,可是咱們有三種方法用於處理狀態:shared mutability(共享可變),isolated mutability(隔離可變性),pure immutability(徹底不可變性)。
一個極端狀況是共享可變性,咱們建立的變量容許全部線程修改。這種在同步方面很容易出錯。
在處理狀態方面,隔離可變性是一個折中的選擇。在該方法中變量是可變的,可是任什麼時候候只有一個線程能夠看到該變量。
另外一個極端的方法是純粹不可變性,該方法全部事物都是不可更改的。要使用這樣的設計很差實現,部分緣由是由問題的性質決定的。