浅谈Java两种并发类型——计算密集型与IO密集型

核心是可以分别独立运行程序指令的计算单元。
线程是操作系统能够进行运算调度的最小单位

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值