Java 进阶7 并行优化 JDK多任务执行框架技术 20131114
         Java 语言本身就是支持多线程机制的,他提供了 Thread  Runnable 接口等简单的多线程支持工具,同时为了进一步改善并发程序的性能,在 JDK中还提供了用于多线程管理的线程池概念。并行优化中,一个重要的知识点就是 线程池技术
ExecutorService exe = Executors.newCachedThreadPool();
1. 无限制线程的缺陷
          多线程设计的软件方法确实可以最大限度的发挥现代多核处理器的计算能力,提高生产系统的吞吐量和性能。但是如果不加控制的随意的使用多线程技术,对于系统性能反而会产生不利的影响,线程的创建,线程上下文的切换需要消耗十分大的系统资源,当线程的数量过多的时候,就会造成很大的系统开销,导致程序的性能降低。
          当我们创建一个线程并且执行完毕的时候, JVM会自动回收线程的资源。在简单的系统中是没有问题的,但是实际的生产过程中,因为实际环境的需要,可能会开启很多的线程资源来支撑业务的需要,这样的话,因为线程数量过大,就会好近 CPU资源,而且忙于线程的切换。虽然线程相比进程轻量级一些,但是线程的创建和回收依然需要花费时间;同时线程资源的创建也是会占用内存空间的,大量的线程会抢占大量的内存资源,如果处理不当的话,会出现内存溢出的情况,导致程序终止,同时大量的线程回收会给GC机制带来很大的负担。
          所以说实际生产的过程中,线程的数量是必须控制的。盲目的大量创建线程的话,对于系统性能影响非常大。
2. 简单线程池的实现
          为了节省系统资源在多线程并发的时候不断的创建和销毁线程的额外开销,就引入了线程池的概念。线程池的基本功能就是线程的复用。当系统接收到一个任务的时候,需要一个线程的时候,不是去创建线程,而是去线程池中查找是否有空闲的线程资源,如果有的话,就会直接使用线程池中的线程执行任务,如果没有的话,才会去创建线程。等待任务完成之后,不是简单的销毁线程资源,而是将现场放入到线程池中空闲队列中,等待下次再次使用。
3.Executor 框架
         JDK 中对于多线程提供一套 Executor框架,帮助开发人员有效的进行线程控制。这些 Class在包 java.util.concurrrent 包中,是 JDK并发包的核心类。其中ThreadPool Executor表示一个线程池。 Executors扮演者线程池工厂的角色,通过 Executors可以取得一个特定功能的线程池。
         Executor : execute
         ExecutorService: shutdown     isShutdown       isTerminated   submit
         AbstractExecutorService:
         ThreadPoolExecutor
         Executors:
ExecutorService exe = Executors.newCachedThreadPool();
for(int I = 0; i< 100; i++){
         exe.submit(new Mythread());
}
这一段代码完成了 100次调度,和前面的线程池差不多;
和简单的线程池相比, Executor框架提供了更多有用的功能。 Executors工厂类提供的主要方法:
public static ExecutorService newFixedThreadPool(in nThreads);
创建固定数量线程的线程池,该线程池中的线程数量始终不会变化。当有一个新的任务提交到线程池中,如果有空闲的线程的话,就会立即执行,否则,新的任务将会被暂存在一个任务队列中,带线程空闲的的时候,便会处理在任务队列中的任务。
public static ExecutorService newSingleThreadExecutor();
该方法创建的线程池中仅仅有一个线程,当超过一个任务被提交到线程池中,那么任务会保存在任务队列中,按照队列的顺序执行任务
public static ExecutorService newCachedThreadPool()
返回一个根据实际情况调整线程数量的线程池。线程池中线程的数量是不确定的,如果有空闲的线程可以使用的话,就会优先使用可以复用的线程,如果没有的话,就会创建新的线程处理任务,所有线程执行完毕之后,不是直接销毁线程,而是返回到线程池中。
public static ScheduledExecutorExecutor newSingleThreadScheduledExecutor():
返回一个 ScheduledExecutorService,线程池的大小事1 ScheduleExecutorService继承自ExecutorService ,扩展了在给定时间内执行某任务的功能,如在某个固定的时间延迟之后执行或者是周期性的执行某些任务,函数原型是:
         schedule(Runnable command, long delay, TimeUnit);
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
相对上一个线程池,就是线程池中的线程的数量是可以自己定义的。
4. 自定义线程池
          其实以上的线程池方法,在底层都是使用的 ThreadPoolExecutor实现的,都是针对ThreadPoolExecutor实现的封装, ThreadPoolExecutor拥有一个强大的封装机制:
public ThreadPoolExecutor(
         int corePoolSize,// 指定线程池中线程的数量
         int maximunPoolSize,// 指定线程池中线程的最大数量
long keepAliveTime,//当线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间
TimeUnit unit, //keepAliveTime参数的单位
BlockingQueue<Runnable> workQueue,// 任务队列,被提交的但是尚未被处理的任务
ThreadFactory threadFactory,//线程工厂,用于创建线程
RejectedExecutionHandler handler);//拒绝策略,当线程池中有太多的任务请求的时候,如何拒绝任务。
JDK中是内置了拒绝策略的:
         AbortPolicy 策略:该策略会直接抛出异常,组织系统长长运行;
         CallerRunsPolicy 策略:只要线程池没有关闭,该策略直接在调用者线程中,运行当前被丢弃的任务;
         DiscardOledestPolicy: 丢弃最老的一个请求,也就是即将被执行的第一个任务,并且尝试再次提交任务;
         DiscardPolicy: 默默的丢弃无法处理的任务,不予任何的理睬。
          以上的策略都是实现了 RejectedExecutionHandler接口,如果依旧是没有满足你的需要的话,那么可以自己定义类实现 RejectedExecutionHandler接口。
public static void main(String[] args) {
        ExecutorService exe = new ThreadPoolExecutor(
                100,
                200,
                0L,
                TimeUnit. SECONDS,
                 new LinkedBlockingQueue<Runnable>());
         for(int i = 0 ;i<1000;i++){
            exe.execute( new Thread("" +i+"" ){
                 public void run(){
                     try {
                        Thread. sleep(100);
                    } catch (InterruptedException e) {
                         // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System. out.println("thread" + this.getName());
                }
            });
        }
    }
}
对于队列的话,可以使用的队列的种类有:
直接提交的队列:使用 SynchronizedQueue实现的,他是没有容量的,而且同步机制会大大降低程序的运行效率,一般不推荐使用;
有界任务队列:使用的是 ArrayBlockingQueue实现的
无界任务队列:使用的是 LinkedBlockingQueue类实现的,除非系统资源好近,否则不会存在任务添加失败的情况。
优先任务队列:使用的是 PriorityBlockingQueue实现的,会根据任务的优先级进行排序,因为这样就会出现比较,所以我们需要对线程实现 Comparable<CompareType>接口中的public int compareTo(CompareType o)方法,才可以使用优先任务队列。
     当内置的线程池无法满足你的需要的时候,就可以考虑自定义线程池
5优化线程池的大小
     线程池的大小对于系统的性能也有十分重要的影响,线程的数量过大或者过小的话,都无法发挥最优的性能,但是线程池的大小也没有必要做的十分精确,只要避免线程数量极大或者是极小的情况既可以了,这样对于系统性能的影响不会很大。线程池容量的大小需要考虑的因素有:CPU 的数量,内存大小, JDBc连接状况等等
     计算公式:
    NThread = Ncpu * Ucpu * (1+ W/C)
其中 Ncpu值得是 CPU的数量,可以使用 Runtime.getRuntime().availableProcessors() 获得计算机中的 CPU数量;
Ucpu指的是目标CPU 的使用率;
W/C 等待时间和计算时间的比值。
6.扩展线程池ThreadPoolExecutor
    ThreadPoolExecutor 他是一个可以扩展的线程池,提供了 beforeExecute afterExecute, terminated()三个接口对线程池进行控制。
 
Tengfei Yang
于广州中山大学 图书馆
20131114
posted on 2013-11-23 12:34  追梦的飞飞  阅读(1999)  评论(0编辑  收藏  举报