转自:http://blog.csdn.net/hereiskxm/article/details/22518065
实际工作中的三类程序适用于以并发的形式来提速:
1. 服务程序:同时响应多个用户请求
2. 计算密集型程序:并发计算,将问题拆分为子任务、并发执行各子任务并最终将子任务的结果汇总合并。
3. IO密集型程序(阻塞型):常需要阻塞等待的程序,比如说因为网络环境阻塞等待,因为IO读取阻塞等待。当一个任务阻塞在IO操作上时,我们可以立即切换执行其他任务或启动其他IO操作请求,这样并发就可以帮助我们有效地提升程序执行效率。
对于IO密集型程序,我们要用并发来获得执行效率的大幅提升时,首先要思考两个问题:如何估计需要创建多少个线程以及如何分解问题。这里也涉及到如何估算并发带来的性能提升的程度。
1. 确定线程数
确定线程数首先需要考虑到系统可用的处理器核心数:
Runtime.getRuntime().availableProcessors();
应用程序最小线程数应该等于可用的处理器核数。如果所有的任务都是计算密集型的,则创建处理器可用核心数这么多个线程就可以了,这样已经充分利用了处理器,也就是让它以最大火力不停进行计算。创建更多的线程对于程序性能反而是不利的,因为多个线程间频繁进行上下文切换对于程序性能损耗较大。
但如果任务都是IO密集型的,那我们就需要创建比处理器核心数大几倍数量的线程。为何?当一个任务执行IO操作时,线程将被阻塞,于是处理器可以立即进行上下文切换以便处理其他就绪线程。如果我们只有处理器核心数那么多个线程的话,即使有待执行的任务也无法调度处理了。
因此,线程数与我们每个任务处于阻塞状态的时间比例相关。加入任务有50%时间处于阻塞状态,那程序所需线程数是处理器核心数的两倍。我们可以计算出程序所需的线程数,公式如下:
线程数=CPU可用核心数/(1 - 阻塞系数),其中阻塞系数在在0到1范围内。
计算密集型程序的阻塞系数为0,IO密集型程序的阻塞系数接近1。
确定阻塞系数,我们可以先试着猜测,或者采用一些性能分析工具或Java.lang.management API 来确定线程花在系统IO上的时间与CPU密集任务所耗的时间比值。
2. 确定任务的数量
我们常常希望各个子任务的工作量是均匀分布的,这样每个线程的负载都差不多。但这通常会花大量的精力去做问题分解。事实证明,把任务尽可能拆解成细粒度,让它远比线程数多,让处理器一直不停地工作,是最实惠的方法。
接下来看一看IO密集型应用程序使用并发的一个实用例子:
这是一个求用户当前股票市值的例子,我们假设在获取用户持有股票信息之后,需向雅虎请求获得该股票的当前市值。
父类,定义了读取输入数据的方式与计时方法,计时方法中调用了solve方法进行处理,并获取执行时间。solve方法由子类实现。
- public abstract class AbstractSolver {
- public static Map<String,Integer> readTickers() throws IOException {
-
- }
-
- public void timeAndCompute() {
- final long start = System.nanoTime();
- final Map<String,Integer> stocks = readTrickers();
- final double result = solve(stocks);
- final long end = System.nanoTime();
- System.out.printf("Number of primes under %d is %d\n", number,
- numberOfPrimes);
- System.out.println("Time (seconds) taken is " + (end - start) / 1.0e9);
- }
-
- public abstract int solve(final Map<String, Integer> stocks) throws InterruptedException,ExcecutionException,IOException;
- }
ConcurrentSolveIO:具体实现。
- public class ConcurrentSolveIO extend AbstractSolver {
- public double solve(final Map<String,Integer> stocks)throws InterruptedException,ExcecutionException {
- final int numberOfCores = Runtime.getRuntime().availableProcessors();
- final double blockingCoefficient = 0.9;
- final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
-
- System.out.println("Number of cores is " + numberOfCores);
- System.out.println("PoolSize is " + poolSize);
- final List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
- for(final String ticker : stocks.keySet()){
- partitions.add(new Callable<Double>(){
- public Double call() throws Exception{
- return stocks.get(triker) * YahooFinance.getPrice(tiker);
- }
- });
- }
-
- final ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
- final List<Future<Double>> valuesOfStocks = ExecutorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
-
- double netAssetValue = 0.0;
- for(final Future<Double> valuesOfStocks : valueOfStocks){
- netAssetValue += valuesOfAStock.get();
- }
-
- executorPool.shutdown();
- return netAssetValue;
- }
-
- public static void main(final String[] aargs) throws ExecutionException, InterruptedException, IOException{
- new ConcurrentSolveIO.timeAndComputeValue();
- }
- }
这个例子中,各个线程之间不需要访问临界区,因为在获得结果后的逻辑较简单,我们在所有任务停止之后一并处理结果。如果在收到外部接口的相应之后还要进行大量计算的话,则最好是在一有可用结果返回时就立即处理。这用JDK中自带的CompletionService可以实现这一功能。