专用通道>

您当前所在位置: 首页 > 行业新闻 > IT技术讨论 >

IT技术讨论

JAVA 线程池

发布者:澳门美高梅网址成都锦江点击: 分享到
一、ThreadPoolExecutor的使用 1.ThreadPoolExecutor的完整构造方法是: ThreadPoolExecutor ( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue Runnable workQueue, RejectedExecutionHandler hand

一、ThreadPoolExecutor的使用

1.ThreadPoolExecutor的完整构造方法是:

ThreadPoolExecutor ( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue< Runnable >  workQueue, RejectedExecutionHandler hander)
申请免费试学】【在线报名在线咨询

2.Executors中的几个工厂方法

ThreadPoolExecutor是Executors类的底层实现

①newFixedThreadPool ( int nThreads)——固定大小线程池

[java]

1
2
3
4
5
public static ExecutorService newFixedThreadPool(intnThreads) { 
        return new ThreadPoolExecutor(nThreads, nThreads, 
                                      0L, TimeUnit.MILLISECONDS, 
                                      newLinkedBlockingQueue<Runnable>()); 
    }

可以看到corePoolSize和maximunPoolSize的大小是一样的(其实使用无界Queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设置表明该实现不想keepAlive,最后的BlockingQueue选择了LinkedBlockingQueue,该Queue有一个特点就是它是无界的;
申请免费试学】【在线报名在线咨询

②newSingleThreadExecutor()——单线程

[java]

1
2
3
4
5
public static ExecutorService newSingleThreadExecutor() { 
        return new ThreadPoolExecutor(11
                                      0L, TimeUnit.MILLISECONDS, 
                                      newLinkedBlockingQueue<Runnable>()); 
    }

申请免费试学】【在线报名在线咨询

③newCachedThreadPool()——无界线程池,可以进行自动线程回收

[java]

 

1
2
3
4
5
public static ExecutorService newCachedThreadPool() { 
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
                                      60L, TimeUnit.SECONDS, 
                                      newSynchronousQueue<Runnable>()); 
    }

在BlockingQueue的选择上使用SynchronousQueue,在该Queue中,每个插入操作必须等待另一个线程的对应移除操作。比如,我先添加一个元素,接下来如果想继续添加则会阻塞,直到另一个线程取走一个元素,反之亦然(也就是缓冲区为1的生产者消费者模式)。
申请免费试学】【在线报名在线咨询

3.corePoolSize、workQueue、maximumPoolSize的优先级

当一个任务通过execute(Runnable)方法欲添加到线程池时: 
如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:

核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
申请免费试学】【在线报名在线咨询

二、BlockingQueue<Runnable> workQueue

1.BlockingQueue的作用

所有的BlockingQueue都可以用于传输和保持提交的任务,可以使用此队列对池大小进行交互。

如果运行的线程小于corePoolSize,则Executor始终首选添加新的线程,而不进行排队,也就是当当前运行的线程小于corePoolSize时,则任务根本不会存放,添加到Queue中,而是直接创建一个新的thread;

如果运行的线程等于或多余corePoolSize,则Executor首选将请求加入Queue,而不是添加新的thread;

如果无法将请求加入Queue,则创建新线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。
申请免费试学】【在线报名在线咨询

2.排队的三种策略

①直接提交,工作队列的默认选项是SynchronousQueue,它将任务交给线程而不保持它们,如果不存在可用于立即执行的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSize以避免拒绝新提交的任务,当命令以超过队列所能处理的平均连续到达时,此策略允许无界线程具有增长的可能性。

②无界队列,使用无界队列如LinkedBlockingQueue将导致在所有corePoolSize线程都忙时新任务在队列中等待,这样创建的线程数就不会超过corePoolSize,因此maximumPoolSize的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列。

③有界队列,当使用有限的maximumPoolSize时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  
申请免费试学】【在线报名在线咨询

3.几个例子

①直接提交策略SynchronousQueue,由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。

[java]

1
2
new ThreadPoolExecutor(2330, TimeUnit.SECONDS,  
            new SynchronousQueue<Runnable>());

   当核心线程已经有2个正在运行.

--此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。

--又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。

--此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。

--暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。

所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。

什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。

②使用无界队列策略,即LinkedBlockingQueue

拿newFixedThreadPool来说,如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。

--如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。

--如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。

--OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,如果任务内存大一些,不一会儿就爆了,呵呵。

③有界队列,即ArrayBlockingQueue

[java]

1
2
new ThreadPoolExecutor( 
             2430, TimeUnit.SECONDS, newArrayBlockingQueue<Runnable>(2));

假设,所有的任务都永远无法执行完。

对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queu中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。
申请免费试学】【在线报名在线咨询

三、用途和用法

为什么要用线程池:

减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;
可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

网络请求通常有两种形式:第一种,请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等。另一种形式是请求频繁,但是连接上以后读/写很少量的数据就断开连接。考虑到服务的并发问题,如果每个请求来到以后服务都为它启动一个线程,那么这对服务的资源可能会造成很大的浪费,特别是第二种情况。因为通常情况下,创建线程是需要一定的耗时的,设这个时间为T1,而连接后读/写服务的时间为T2,当T1>>T2时,我们就应当考虑一种策略或者机制来控制,使得服务对于第二种请求方式也能在较低的功耗下完成。

通常,我们可以用线程池来解决这个问题,首先,在服务启动的时候,我们可以启动好几个线程,并用一个容器(如线程池)来管理这些线程。当请求到来时,可以从池中去一个线程出来,执行任务(通常是对请求的响应),当任务结束后,再将这个线程放入池中备用;如果请求到来而池中没有空闲的线程,该请求需要排队等候。最后,当服务关闭时销毁该池即可。
申请免费试学】【在线报名在线咨询

 

澳门美高梅网址咨询老师

澳门美高梅网址成都锦江校区介绍

澳门美高梅网址成都锦江校区是澳门美高梅网址总部在成都设立的一家示范校区。涵盖ACCP、Java、.Net、网络营销、市场营销,游戏开发等多专业校区,承担美高梅娱乐场、美高梅娱乐场示范、以及美高梅娱乐场培养输送等职责。
澳门美高梅网址作为北京大学下属的专业澳门美高梅网址澳门美高梅网址,以北京大学强大师资作为依托,连续13年被评为“中国IT教育第一品牌”,累计培养60+万优秀软件工程师,是名符其实的软件工程师的摇篮……请认准品牌名校——澳门美高梅网址成都锦江校区,地址:成都市春熙路北口东行500米(大慈寺22号)。