Java中的线程池

Java中的线程池

使用线程池的优点:降低资源消耗、提高响应速度、提高线程的可管理性。

线程池原理

线程池处理流程:

if (核心线程池没满){
   
//创建线程执行,执行完后会去工作队列中获取新的任务task.run()
}else if(工作队列没满){
   
//将提交的任务存储在工作队列里
}else if (线程池没满){
   
//创建线程执行任务
}else {
   
//其他策略处理无法执行的任务
}

对应下面的源码

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

工作线程:线程池工作时会将线程封装成工作线程Worker,Worker在执行完任务后,会循环获取工作队列里的任务来执行。

构造函数

public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
        BlockingQueue < Runnable > workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

参数:

corePoolSize,保存在池中的线程数。

maximumPoolSize,允许的最大线程数,如果队列满了,线程数小于最大线程数,将创建新线程。

keepAliveTime,如果线程数量大于核心数量,若超过这个时间还没有新的任务将会终止多余的空闲线程。

unit,时间单位。

workQueue,用于保存等待执行的任务的阻塞队列。

threadFactory,executor创建线程时使用的工厂。

handler,处理因为饱和无法执行任务的策略,包括直接抛异常、直接丢弃、丢弃队列里最近的任务、只用调用者所在的线程执行任务。

线程池的使用

在http://blog.youkuaiyun.com/qq407388356/article/details/78535703提到过线程池的一些用法,这里复习整合一下。使用线程池分别提交实现没有返回值的Runable接口和带有返回值的Callable<>接口。

在Java5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callablecall()方法只能通过ExecutorServicesubmit(Callable<T>task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的Future

Callable中的call()方法类似Runnable的run()方法,就是前者有返回值,后者没有。

当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。

同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by SunLin on 2018.3.13
 */
public class test_ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<Integer>[] fts = new Future[5];
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 5; i++) {
            exec.execute(new RunnableTask1("Runnable" + i));
            fts[i]=exec.submit(new RunnableTask2("Callable"+i));
            //这句放在这里,将会阻塞循环运行,需要等到得到返回值fts[i].get()后才能再继续
            //System.out.println("Callable"+i+"运行时间"+fts[i].get());
        }
        for (int i=0;i<fts.length;i++){
            System.out.println("Callable"+i+"运行时间"+fts[i].get());
        }
        exec.shutdown();

    }
}

class RunnableTask1 implements Runnable {
    private String threadName;

    RunnableTask1(String name) {
        threadName = name;
        System.out.println("Creating " + threadName);
    }

    public void run() {
        try {
            int time = new Random().nextInt(100);
            Thread.sleep(time*time);
            System.out.println("Running " + threadName+" 运行时间+"+time*time);
        } catch (InterruptedException e) {
            System.out.println(threadName + " interrupted.");
        }
        System.out.println("Thread " + threadName + " exiting.");
    }
}

class RunnableTask2 implements Callable<Integer>{
    private String threadName;

    RunnableTask2(String name) {
        threadName = name;
        System.out.println("Creating " + threadName);
    }
    @Override
    public Integer call() throws Exception {
        int time = new Random().nextInt(100);
        Thread.sleep(time*time);
        System.out.println("Running " + threadName);
        return time*time;
    }
}

execute和submit区别

1、接收的参数不一样

2、submit有返回值,而execute没有

3、submit方便Exception处理

如果你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

配置线程池考虑的因素

考虑一下因素:任务的性质(CPU密集、IO密集和混合型)、任务优先级、任务时间长短、任务依赖其他资源(如数据库)。

CPU密集型应配置尽可能小的线程,如配置CPU数量+1个线程的线程池。

IO密集型可以考虑2*CPU数量。

混合型的可以拆分成两个类型的任务。

优先级可以使用优先队列PriorityBlockingQueue来处理,可以让优先级高的任务先执行。

执行时间长短可以交给不同规模的线程池来处理,或者选用优先队列。

依赖资源(如数据库连接),可以配置尽量大的线程数量,以更好利用CPU资源。

 

同时可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法(这几个是空方法),可以对线程池进行监控。

Executor框架介绍

Executor框架主要包括三个部分:

任务

Runnable接口和Callable接口,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。

执行

核心接口Executor(将任务提交和执行分离),继承Executor的ExecutorService接口。

ThreadPoolExecutor(线程池的核心实现类,用来执行被提交的任务)和ScheduledThreadPoolExecutor(可以在给定的延迟后执行)实现了这个接口。

异步计算结果

Future接口和实现该接口的FutureTask实现类(通过ExecutorService.submit()返回该类型)。

ThreadPoolExecutor

ThreadPoolExecutor通常使用工厂类Executors来创建,是最核心的类。

可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。

SingleThreadExecutor

使用单个worker线程的线程池,相当于FixedThreadPool(1)。

FixedThreadPool

可重用固定线程数的线程池。corePoolSize和maximumPoolSize都设置为指定参数n,使用无界队列作为线程池工作队列(容量为Integer.MAX_VALUE)。

CachedThreadPool

根据需要创建新线程的线程池。corePoolSize设置为0,maximumPoolSize设置为Integer.MAX_VALUE。keepAliveTime设置为60L,表示空闲线程超过60秒会被终止。使用没有容量的SynchronousQueue作为工作队列。这个工作队列不是很好理解,没有容量,装载的不是等待执行的任务(因为任务不需要等待,maximumPoolSize=Integer.MAX_VALUE),而是空闲线程和提交任务的一个匹配。步骤如下:

1、首先执行线程execute()会执行SynchronousQueue.offer(Runnable task),如果当前有空闲线程正在执行SynchronousQueue.poll(),那么offer和poll操作配对成功,任务交给该worker线程执行。否则步骤2。

2、如果没有空线程,则没有poll操作来匹配,此时会创建一个新的线程来执行待执行的任务。

3、步骤2中新建的线程执行完后会执行SynchronousQueue.poll()操作,如果在这个操作中等待60秒没有出现offer操作的话,则视为空闲线程超过等待60而被终止。

总体可以这样理解:一个工地需要搬运工(worker线程),都在工地上(SynchronousQueue)等待搬运的工作(poll),如果来了任务(offer)则会调一个搬运工(worker)进行执行,而其他搬运工由于等待时间太久没工作干就散了,工作量增加时会再召集新的搬运工。

ScheduledThreadPoolExecutor

继承自ThreadPoolExecutor,使用工厂类Executors创建,主要用来给定的延迟之后运行任务,或者说定期执行任务。ScheduledThreadPoolExecutor功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大,Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor对应多个后台线程数。

调用scheduleAtFixedRate()或scheduleWithFixedDelay()后会向DelayQueue添加一个ScheduledFutureTask。线程池中线程从DelayQueue中获取一个Task执行。

ScheduledFutureTask主要包含3个成员变量:long time(被执行的具体时间)、long sequenceNumber(被添加后的序号)、long period(任务执行的间隔周期)。

DelayQueue封装了PriorityQueue,先比较time小的在前面,相等比较sequenceNumber小的在前(也就是执行时间一样,先提交的先执行)。

SingleThreadScheduledExecutor,则是具有单个的线程。

Future接口和FutureTask类

Future接口和实现类FutureTask类代表异步计算的结果。

FutureTask除了实现Future接口外还实现了Runnable接口。

当FutureTask处于未启动或已启动但未结束时,调用FutureTask.get()将导致调用线程阻塞(等待结果);当处于已完成状态时,执行FutureTask.get()将导致调用线程立即返回结果或抛出异常。

参考《Java并发编程的艺术》——方腾飞、魏鹏、程晓明

BlockingQueue、Semaphore、CountDownLatch、CyclicBarrier

BlockingQueue

如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒,同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间时才会被唤醒继续操作。 BlockingQueue有四个具体的实现类,

ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小。其所含的对象是以FIFO(先入先出)顺序排序的。

LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。其所含的对象是以FIFO顺序排序的。

PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。

Semaphore

Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,acquire()获取一个许可,如果没有就等待,而release()释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

CountdownLatch

CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。(当每个线程调用countdown方法直到将countdownlatch方法创建时数减为0时,那么之前调用await()方法的线程才会继续执行。有一点注意,那就是只执行一次,不能到0以后重新执行)

CyclicBarrier

类有一个整数初始值,此值表示将在同一点同步的线程数量。当其中一个线程到达确定点,它会调用await() 方法来等待其他线程。当线程调用这个方法,CyclicBarrier阻塞线程进入休眠直到其他线程到达。当最后一个线程调用CyclicBarrier 类的await() 方法,它唤醒所有等待的线程并继续执行它们的任务。(当等待的线程数量达到CyclicBarrier线程指定的数量以后(调用await方法的线程数),才一起往下执行,否则大家都在等待,注意:如果达到指定的线程数量的时候:则可以重新计数,上面的过程可以循环)

CountDownLatch是减计数方式,计数==0时释放所有等待的线程;CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。

### 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、付费专栏及课程。

余额充值