第一章、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(彻底不可变性)。
一个极端状况是共享可变性,咱们建立的变量容许全部线程修改。这种在同步方面很容易出错。
在处理状态方面,隔离可变性是一个折中的选择。在该方法中变量是可变的,可是任什么时候候只有一个线程能够看到该变量。
另外一个极端的方法是纯粹不可变性,该方法全部事物都是不可更改的。要使用这样的设计很差实现,部分缘由是由问题的性质决定的。