淺談Java兩種並發類型——計算密集型與IO密集型


轉載:https://blog.csdn.net/u013070853/article/details/49304099

核心是可以分別獨立運行程序指令的計算單元。
線程是操作系統能夠進行運算調度的最小單位。

PS:4核心8線程的!等於你有4個倉庫,你要運輸貨物,8線程就是高速公路!8條高速公路送比你4條高速公路運的快吧!

有一個原則是:活躍線程數為 CPU(核)數時最佳。過少的活躍線程導致 CPU 無法被充分利用,過多的活躍線程導致過大的線程上下文切換開銷。

線程應該是活躍的,處於 IO 的線程,休眠的線程等均不消耗 CPU。

在Java並發編程方面,計算密集型與IO密集型是兩個非常典型的例子,這次大象就來講講自己在這方面的內容,本篇比較基礎,只適合剛入門的童鞋,請各種牛人不喜勿噴。

    計算密集型
    計算密集型,顧名思義就是應用需要非常多的CPU計算資源,在多核CPU時代,我們要讓每一個CPU核心都參與計算,將CPU的性能充分利用起來,這樣才算是沒有浪費服務器配置,如果在非常好的服務器配置上還運行着單線程程序那將是多么重大的浪費。對於計算密集型的應用,完全是靠CPU的核數來工作,所以為了讓它的優勢完全發揮出來,避免過多的線程上下文切換,比較理想方案是:
    線程數 = CPU核數+1
    也可以設置成CPU核數*2,這還是要看JDK的使用版本,以及CPU配置(服務器的CPU有超線程)。對於JDK1.8來說,里面增加了一個並行計算,計算密集型的較理想線程數 = CPU內核線程數*2
計算文件夾大小算是一個比較典型的例子,代碼很簡單,我就不多解釋了。

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 計算文件夾大小
 * @author 菠蘿大象
 */
public class FileSizeCalc {

    static class SubDirsAndSize {
        public final long size;
        public final List<File> subDirs;

        public SubDirsAndSize(long size, List<File> subDirs) {
            this.size = size;
            this.subDirs = Collections.unmodifiableList(subDirs);
        }
    }
    
    private SubDirsAndSize getSubDirsAndSize(File file) {
        long total = 0;
        List<File> subDirs = new ArrayList<File>();
        if (file.isDirectory()) {
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    if (child.isFile())
                        total += child.length();
                    else
                        subDirs.add(child);
                }
            }
        }
        return new SubDirsAndSize(total, subDirs);
    }
    
    private long getFileSize(File file) throws Exception{
        final int cpuCore = Runtime.getRuntime().availableProcessors();
        final int poolSize = cpuCore+1;
        ExecutorService service = Executors.newFixedThreadPool(poolSize);
        long total = 0;
        List<File> directories = new ArrayList<File>();
        directories.add(file);
        SubDirsAndSize subDirsAndSize = null;
        try{
            while(!directories.isEmpty()){
                List<Future<SubDirsAndSize>> partialResults= new ArrayList<Future<SubDirsAndSize>>();
                for(final File directory : directories){
                    partialResults.add(service.submit(new Callable<SubDirsAndSize>(){
                        @Override
                        public SubDirsAndSize call() throws Exception {
                            return getSubDirsAndSize(directory);
                        }
                    }));
                }
                directories.clear();
                for(Future<SubDirsAndSize> partialResultFuture : partialResults){
                    subDirsAndSize = partialResultFuture.get(100,TimeUnit.SECONDS);
                    total += subDirsAndSize.size;
                    directories.addAll(subDirsAndSize.subDirs);
                }
            }
            return total;
        } finally {
            service.shutdown();
        }
    }
    
    public static void main(String[] args) throws Exception {
        for(int i=0;i<10;i++){
            final long start = System.currentTimeMillis();
            long total = new FileSizeCalc().getFileSize(new File("e:/m2"));
            final long end = System.currentTimeMillis();
            System.out.format("文件夾大小: %dMB%n" , total/(1024*1024));
            System.out.format("所用時間: %.3fs%n" , (end - start)/1.0e3);
        }
    }
}


    執行10次后結果如下:
    
    在上面的例子中,線程池設置為CPU核心數+1個,這個運行結果是大象在工作電腦(CPU:G630 內存:4G JDK1.7.0_51)上跑出來的。如果在這里把線程池加大,比如調到100,你會發現所用時間變多了,大象這里最多的消耗時間是0.297秒,與之前最少的一次0.218之間相差0.079秒,也即79毫秒。當然這多出來的時間在我們看來好像不算什么,只有零點零幾秒,但是對於CPU來說可是相當長的,因為CPU里面是以納秒為計算單位,1毫秒=1000000納秒。所以加大線程池會增加CPU上下文的切換成本,有時程序的優化就是從這些微小的地方積累起來的。
    IO密集型
    對於IO密集型的應用,就很好理解了,我們現在做的開發大部分都是WEB應用,涉及到大量的網絡傳輸,不僅如此,與數據庫,與緩存間的交互也涉及到IO,一旦發生IO,線程就會處於等待狀態,當IO結束,數據准備好后,線程才會繼續執行。因此從這里可以發現,對於IO密集型的應用,我們可以多設置一些線程池中線程的數量,這樣就能讓在等待IO的這段時間內,線程可以去做其它事,提高並發處理效率。
    那么這個線程池的數據量是不是可以隨便設置呢?當然不是的,請一定要記得,線程上下文切換是有代價的。目前總結了一套公式,對於IO密集型應用:
    線程數 = CPU核心數/(1-阻塞系數)
    這個阻塞系數一般為0.8~0.9之間,也可以取0.8或者0.9。套用公式,對於雙核CPU來說,它比較理想的線程數就是20,當然這都不是絕對的,需要根據實際情況以及實際業務來調整。
    final int poolSize = (int)(cpuCore/(1-0.9))
    本篇大象簡單談了下並發類型,旨在拋磚引玉,讓初學並發編程的朋友能夠有一些了解,說的不對的地方,還請各位指出來。
    嘮叨完上面這些,再嘮叨下JDK的版本,每次Java的版本升級,就意味着虛擬機以及GC的性能都有一定程度的提升,所以JDK1.7比JDK1.6在並發處理速度上要更快一些,注意對多線程程度請加上-server參數,並發效果更好一些。現在JDK1.8都出來這么久了,你的JDK是不是應該升級下了呢?
    本文為菠蘿大象原創,如要轉載請注明出處。http://www.blogjava.net/bolo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM