java中线程池

来源:互联网 发布:雷姆雷姆软件汉化 编辑:程序博客网 时间:2024/06/06 07:10

java中线程池的实现在jdk1.5以上版本提供了ThreadPoolExecutor类,该类继承了抽象类AbstractExecutorService,是接口
Executor的底层实现类。
那么这里首先了解下Executor。jdk文档中说明了Executor接口执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用 Executor 而不是显式地创建线程。例如,可能会使用以下方法:
 Executor executor = anExecutor;
 executor.execute(new RunnableTask1());
 executor.execute(new RunnableTask2());
而不是为一组任务中的每个任务调用 new Thread(new(RunnableTask())).start()。由此可见Executor简化了线程的创建,调度、撤销等,降低了线程因创建和撤销而花费的系统开销。

ThreadPoolExecutor的完整构造方法是:
 
ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   ThreadFactory threadFactory,
                   RejectedExecutionHandler handler
                   )
下面对其参数进行说明:
corePoolSize: 线程池维护线程的正常数目,即线程池的正常大小。
maximumPoolSize:线程池维护线程的最大数量。当线程池满了(即线程池中有corePoolSize个线程了)同时缓冲队列也满了情况下,如果有新的任务来临,可以开辟新的线程来执行任务,但                 是总的线程数目不能超过maximumPoolSize
keepAliveTime: 线程池维护线程所允许的空闲时间。如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。从而减少了资源消耗。
unit: 线程池维护线程所允许的空闲时间的单位。即制定keepAliveTime的时间单位。
workQueue: 线程池所使用的缓冲队列。当有任务来临时,如果当前线程池的活动线程数目大于等于corePoolSize时,同时缓冲队列未满时,则将任务添加到该队列中。
handler: 线程池对拒绝任务的处理策略。所谓拒绝,即当前线程池中活动的线程数目等于maxinumPoolSize,同时队列满时,则线程池对任务拒绝。该处理策略可以自己编写,也可以使用jdk中提供的四种策略,注意这四种策略要慎重选择,选择不当可能导致你不想要的结果。
下面对该四种策略进行介绍:
1、ThreadPoolExecutor.AbortPolicy。该策略为默认的策略。即任务遭到拒绝时抛出运行时异常RejectedExecutionException。
2、ThreadPoolExecutor.CallerRunsPolicy。该策略绕过线程池,直接在添加任务的线程(即调用execute方法的线程,execute方法将在下面降到)执行被拒绝的任务,说实话我还不知道在什么情况下会用到该策略。
3、ThreadPoolExecutor.DiscardPolicy。该策略比较简单,直接丢掉被拒绝的任务。
4、ThreadPoolExecutor.DiscardOldestPolicy。该策略丢掉老任务,保留新任务。具体就是从任务缓冲队列头部删除没有被执行的任务,然后将新到的任务添加到队列中。


了解了它的构造函数以及相关参数以后以后,下面我们来看看怎么将任务添加到线程池的。
当有任务到达时,通过 execute(Runnable)方法被添加到线程池,这里可以看到所谓的任务其实就是实现了接口Runnable的类型对象,任务的具体工作其实就是在run()方法里执行。
当一个任务通过execute(Runnable)方法欲添加到线程池时,根据你线程池和所选择的拒绝策略对任务进行相应的处理,具体如下:
1、线程池中的任务数量小于corePoolSize,则任务直接被执行
2、线程池中的任务数量大于等于corePoolSize,同时workQueue未满,则将任务添加到缓冲队列workQueue中。
3、线程池中任务数量大于等于corePoolSize,workQueue同时满,但是线程池中运行的任务数目小于maxinumPoolSize时,则重新开辟线程执行新任务。
4、线程池中的任务数量大于等于corePoolSize,workQueue也满了,同时maxinumPoolSize也超了,那么新任务将被拒绝,并根据相应的拒绝策略来进行处理。相应的拒绝策略处理在上面有所介绍。
介绍完这些以后,下面我们看一个列子:

 

import java.io.Serializable;  
import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  
    
public class TestThreadPool2  
{  
    privatestatic int produceTaskSleepTime = 3;  
    privatestatic int produceTaskMaxNumber = 10;  
    
    publicstatic void main(String[] args)  
    {  
        // 构造一个线程池  
        ThreadPoolExecutor threadPool =new ThreadPoolExecutor(2,4, 5, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3),  
                newThreadPoolExecutor.CallerRunsPolicy());  
    
        for(int i = 1; i <= produceTaskMaxNumber; i++)  
        {  
            try  
            {  
                // 产生一个任务,并将其加入到线程池  
                String task ="task@ " + i;  
                System.out.println("put "+ task);  
                threadPool.execute(newThreadPoolTask(task));  
    
                // 便于观察,等待一段时间  
//                Thread.sleep(produceTaskSleepTime);  
            }  
            catch(Exception e)  
            {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
    
/** 
 * 线程池执行的任务 
 */  
class ThreadPoolTask implements Runnable, Serializable  
{  
    privatestatic final long serialVersionUID = 0;  
    privatestatic int consumeTaskSleepTime = 2000;  
    // 保存任务所需要的数据  
    privateObject threadPoolTaskData;  
    
    ThreadPoolTask(Object tasks)  
    {  
        this.threadPoolTaskData = tasks;  
    }  
    
    publicvoid run()  
    {  
        // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句  
        System.out.println("当前运行的线程是:"+threadPoolTaskData);  
        System.out.println("start .."+ threadPoolTaskData);  
    
        try  
        {  
            // //便于观察,等待一段时间  
            Thread.sleep(consumeTaskSleepTime);  
        }  
        catch(Exception e)  
        {  
            e.printStackTrace();  
        }  
        System.out.println("线程:"+threadPoolTaskData+"运行结束"); 
        threadPoolTaskData =null;  
//        System.out.println("线程:"+threadPoolTaskData+"运行结束");  
    }  
    
    publicObject getTask()  
    {  
        returnthis.threadPoolTaskData;  
    }  
}


思考:
1、当新任务到达时,execute(Runnable)处理方式有四种情况,其中说到的第三种情况,是否会导致后来的任务先运行的情况。
   这里肯定会出现这种情况的,即第三种情况中,后来的任务会先于缓冲队列中的任务执行。通常我们都会让任务有个先来后,那么如何避免出现这种情况呢,其实我们可以设置corePoolSize=maxinumPoolSize。
2、关于缓冲队列的思考。
   通过查阅jdk文档知道BlockingQueue是个接口,支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。 那么它的具体实现有哪些呢?
(1)ArrayBlockingQueue,一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。个人不建议线程池中使用这种队列,因为线程池中涉及到频繁的删除插入操作(即出队入队操作),这样难免涉及到元素的移动,而这是一个消耗资源的操作。
(2)LinkedBlockingQueue,一个基于已链接节点的、范围任意的 blocking queue。此队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。在线程池中本人侧重喜欢使用这种队列。
(3)SynchronousQueue,一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。也即队列中只有存在一个元素。
(4)DelayQueue,该队列是一个延迟队列,即队列中的每一个元素只有延迟期满之后才能被take出来。
(5)LinkedBlockingDeque,双向并发阻塞队列。所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条件时挂起线程。每一个结点有前后两个引用,这样才能将所有元素串联起来,支持双向遍历。
(6) PriorityBlockingQueue,要了解PriorityBlockingQueue我们首先了解PriorityQueue,那么PriorityQueue是什么队列呢?它是一个基于优先级堆的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。优先级队列不允许使用 null 元素。依靠自然顺序的优先级队列还不允许插入不可比较的对象(这样做可能导致 ClassCastException)。 此队列的头 是按指定排序方式确定的最小 元素。如果多个元素都是最小值,则头是其中一个元素——选择方法是任意的。队列获取操作 poll、remove、peek 和 element 访问处于队列头的元素。而PriorityBlockingQueue一个无界阻塞队列,它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞获取操作。

 

 


 

原创粉丝点击