java中线程池

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  
{  
    private static int produceTaskSleepTime = 3;  
    private static int produceTaskMaxNumber = 10;  
    
    public static void main(String[] args)  
    {  
        // 构造一个线程池  
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),  
                new ThreadPoolExecutor.CallerRunsPolicy());  
    
        for (int i = 1; i <= produceTaskMaxNumber; i++)  
        {  
            try  
            {  
                // 产生一个任务,并将其加入到线程池  
                String task = "task@ " + i;  
                System.out.println("put " + task);  
                threadPool.execute(new ThreadPoolTask(task));  
    
                // 便于观察,等待一段时间  
//                Thread.sleep(produceTaskSleepTime);  
            }  
            catch (Exception e)  
            {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
    
/** 
 * 线程池执行的任务 
 */  
class ThreadPoolTask implements Runnable, Serializable  
{  
    private static final long serialVersionUID = 0;  
    private static int consumeTaskSleepTime = 2000;  
    // 保存任务所需要的数据  
    private Object threadPoolTaskData;  
    
    ThreadPoolTask(Object tasks)  
    {  
        this.threadPoolTaskData = tasks;  
    }  
    
    public void 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+"运行结束");  
    }  
    
    public Object getTask()  
    {  
        return this.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 相同的顺序规则,并且提供了阻塞获取操作。

 

 


 

### Java 中线程池的使用方法及示例代码 Java 中的线程池主要用于管理和重用线程资源,从而提高应用程序的性能和响应速度。以下是关于线程池的具体实现方式及相关示例代码。 #### 一、线程池的核心概念 线程池的主要目的是减少每次创建新线程所带来的开销,并允许更高效地管理并发任务。核心组件包括 `Executor` 接口及其子接口 `ExecutorService`,以及具体的实现类如 `ThreadPoolExecutor` 和一些便捷工厂方法(如 `Executors.newFixedThreadPool()` 等)[^1]。 --- #### 二、常用的线程池类型及其实现 ##### 2.1 FixedThreadPool 此线程池具有固定的线程数量,超出的任务会被放入队列中等待执行。 ```java // 创建一个固定大小为3的线程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { final int taskNumber = i; fixedThreadPool.execute(() -> { System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName()); }); } fixedThreadPool.shutdown(); // 关闭线程池 ``` 这种方式适用于需要严格控制并发线程数的情况[^2]。 --- ##### 2.2 CachedThreadPool 此类线程池会根据需要动态创建新的线程,但在空闲时会回收未使用的线程。 ```java ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int taskIndex = i; cachedThreadPool.submit(() -> { try { Thread.sleep(100); // 模拟耗时操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Executing Task " + taskIndex); }); } cachedThreadPool.shutdown(); ``` 适合短生命周期的小型任务场景。 --- ##### 2.3 SingleThreadExecutor 仅有一个工作线程,按顺序依次执行提交的任务。 ```java ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); singleThreadExecutor.submit(() -> System.out.println("First Task")); singleThreadExecutor.submit(() -> System.out.println("Second Task")); singleThreadExecutor.shutdown(); ``` 常用于串行化任务处理。 --- ##### 2.4 ScheduledThreadPool 能够安排命令在未来某个时刻执行,也可以周期性地重复执行某项任务。 ```java ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2); scheduledThreadPool.schedule(() -> System.out.println("Delayed Task"), 2, TimeUnit.SECONDS); scheduledThreadPool.scheduleAtFixedRate(() -> { System.out.println("Periodic Task at " + LocalDateTime.now()); }, 0, 1, TimeUnit.SECONDS); scheduledThreadPool.shutdown(); ``` 对于定时任务非常有用。 --- ##### 2.5 WorkStealingPool (JDK 1.8 新增) 利用 ForkJoinPool 技术实现的工作窃取算法,旨在最大化 CPU 利用率。 ```java ExecutorService workStealingPool = Executors.newWorkStealingPool(); IntStream.rangeClosed(1, 5).forEach(i -> { workStealingPool.submit(() -> { System.out.println("Task-" + i + " executed by " + Thread.currentThread().getName()); }); }); workStealingPool.shutdown(); ``` 推荐在高吞吐量计算密集型环境中采用。 --- #### 三、手动创建线程池 (`ThreadPoolExecutor`) 如果默认提供的几种线程池无法完全满足业务需求,则可通过 `ThreadPoolExecutor` 自定义更多细节参数。 ```java ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor( 2, // 核心线程数 corePoolSize 4, // 最大线程数 maximumPoolSize 60L, // 空闲线程存活时间 keepAliveTime TimeUnit.SECONDS, // 时间单位 unit new LinkedBlockingQueue<>(100)); // 任务队列 queue customThreadPool.execute(() -> System.out.println("Custom Pool Example")); customThreadPool.shutdown(); ``` 通过调整这些参数可以灵活应对各种复杂情况。 --- ### 总结 以上分别介绍了五种典型的线程池类型及其适用范围,并提供了相应的代码片段帮助理解其基本用法。合理选用合适的线程池有助于优化系统的整体表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值