Java
进阶
7
并行优化
JDK
多任务执行框架技术
20131114
Java
语言本身就是支持多线程机制的,他提供了
Thread
类
Runnable
接口等简单的多线程支持工具,同时为了进一步改善并发程序的性能,在
JDK
中还提供了用于多线程管理的线程池概念。并行优化中,一个重要的知识点就是 线程池技术。
ExecutorService exe = Executors.newCachedThreadPool();
1.
无限制线程的缺陷
多线程设计的软件方法确实可以最大限度的发挥现代多核处理器的计算能力,提高生产系统的吞吐量和性能。但是如果不加控制的随意的使用多线程技术,对于系统性能反而会产生不利的影响,线程的创建,线程上下文的切换需要消耗十分大的系统资源,当线程的数量过多的时候,就会造成很大的系统开销,导致程序的性能降低。
当我们创建一个线程并且执行完毕的时候,
JVM
会自动回收线程的资源。在简单的系统中是没有问题的,但是实际的生产过程中,因为实际环境的需要,可能会开启很多的线程资源来支撑业务的需要,这样的话,因为线程数量过大,就会好近
CPU
资源,而且忙于线程的切换。虽然线程相比进程轻量级一些,但是线程的创建和回收依然需要花费时间;同时线程资源的创建也是会占用内存空间的,大量的线程会抢占大量的内存资源,如果处理不当的话,会出现内存溢出的情况,导致程序终止,同时大量的线程回收会给
GC
机制带来很大的负担。
所以说实际生产的过程中,线程的数量是必须控制的。盲目的大量创建线程的话,对于系统性能影响非常大。
2.
简单线程池的实现
为了节省系统资源在多线程并发的时候不断的创建和销毁线程的额外开销,就引入了线程池的概念。线程池的基本功能就是线程的复用。当系统接收到一个任务的时候,需要一个线程的时候,不是去创建线程,而是去线程池中查找是否有空闲的线程资源,如果有的话,就会直接使用线程池中的线程执行任务,如果没有的话,才会去创建线程。等待任务完成之后,不是简单的销毁线程资源,而是将现场放入到线程池中空闲队列中,等待下次再次使用。
3.Executor
框架
(
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
)
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
Java 进阶7 并行优化 JDK多任务执行框架技术
原文作者:java 线程池
原文地址: https://www.cnblogs.com/hbhzsysutengfei/p/3438854.html
本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
原文地址: https://www.cnblogs.com/hbhzsysutengfei/p/3438854.html
本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。