赞
踩
为什么要使用线程池?
Java标准库中的线程池
实现一个线程池
提前将线程创建好, 存在线程池中, 后面如果要使用线程可以直接从线程池中获取, 而不必从系统中申请, 线程结束之后也不是由系统销毁, 而是继续存入线程池中, 这样, 线程创建和销毁的速度会加快.
那么, 为什么在线程池里取出线程比系统创建线程要快呢?
操作系统中存在两种状态, 用户态和内核态, 程序中的部分指令需要调用操作系统的API, 进一步的逻辑都会在内核中执行, 这种代码就是内核态的代码.
创建线程本身就需要内核的支持(创建线程就是在内核中创建一个PCB, 并将PCB串在链表上), 包括开启线程(Thread.start)也需要进入内核态来进行.
而将创建好的线程加入线程池中, 由于线程池是在用户态下实现的, 在线程池中存放/取出线程, 这个过程不需要涉及到内核态.
一般认为, 纯用户态的操作, 效率高于经过内核态处理的操作. 纯用户态的操作是可控的, 而内核态的操作整体是不可控的, 我们这里提到的内核态操作的效率更低, 指的是内核态的执行过程是不可控的, 内核态有可能在执行这条指令的时候也执行了其他的指令, 所以说纯用户态的操作效率高于经过内核态的操作.
Java中描述线程池的类叫做"ThreadPoolExecutor", 这个类位于java.util.concurrent包中, 共有四种构造方法:
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)
创建一个新的 ThreadPoolExecutor, 给定初始参数和默认线程工厂和拒绝执行处理程序。- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
RejectedExecutionHandler handler) 创建一个新的
ThreadPoolExecutor与给定的初始参数和默认线程工厂。- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
ThreadFactory threadFactory) 创建一个新的
ThreadPoolExecutor, 给定的初始参数和默认拒绝执行处理程序。- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler) 创建一个新
ThreadPoolExecutor, 给定初始参数。
看起来有点难理解, 我们只需要关注第四个构造方法中的参数即可:
int corePoolSize:核心线程数
int maximumPoolSize:最大线程数
long keepAliveTime:非核心线程等待的最大时间
TimeUnit unit:时间单位(s, ms, us)
BlockingQueue workqueue:任务队列(线程池会提供一个submit()方法, 将任务注册到线程池中, 也就是加入这个任务队列中)
ThreadFactory threadFactory:线程工厂(线程的创建)
RejectedExecutionHandler handler:拒绝策略(线程池满了之后应该怎么做)
线程池的参数非常多, 其中最重要的是线程的个数问题.
有一个程序要并发地去执行一些任务, 如果使用线程的话, 如何设置线程池的大小?
通过性能测试找到一个合适的值, 例如:写一个服务器程序, 服务器里通过线程池, 多线程的处理用户请求, 就可以对这个服务器进行性能测试, 比如构造一些请求, 发送给服务器, 要测试性能, 这里的请求就要构造很多, 然后根据实际的业务场景, 构造合适的请求个数
根据不同的线程数, 来观察持有的CPU的占用率, 从而找到一个速度能被用户接受, CPU占用率也合理的平衡点(对于线上服务器来说, 为了应对一些突发情况, 需要CPU留有一定的冗余, 因此CPU的占用率不能过高)
标准库中还提供了一个简化版的线程池 - Executors, 本质是对ThreadPoolExecutor进行封装, 提供一些默认参数.
public static void main(String[] args) {
//创建一个固定大小的线程池, 参数决定了线程池中线程的最大个数
ExecutorService pool = Executors.newFixedThreadPool(10);
//创建一个自动扩容的线程池
Executors.newCachedThreadPool();
//创建一个只有一个线程的线程池
Executors.newSingleThreadExecutor();
//创建一个具有定时器功能的线程池
Executors.newScheduledThreadPool(3000);
pool.submit(new Runnable() {
@Override
public void run() {
//执行的任务
}
});
}
实现线程池需要:
具体实现代码:
class MyThreadPool{
//1. 使用Runnable描述任务
//2. 使用阻塞队列组织任务
private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>();
//3. 描述一个线程
static class Worker extends Thread{
private BlockingQueue<Runnable> blockingQueue = null;
public Worker(BlockingQueue<Runnable> blockingQueue){
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true){
try {
//循环地获取任务队列中的任务
//如果队列不为空, 获取到队首元素, 队列为空则产生阻塞
Runnable runnable = blockingQueue.take();
//执行任务
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//4. 组织若干个线程
private List<Thread> workers = new ArrayList<>();
//在构造方法中创建若干个线程放到上面的顺序表中, n代表线程的个数
public MyThreadPool(int n) {
for(int i=0;i<n;++i){
Worker worker = new Worker(blockingQueue);
worker.start();
workers.add(worker);
}
}
//5. 创建一个方法, 允许线程进入线程池
public void submit(Runnable runnable){
try {
blockingQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
The end
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。