前言:
大家好,我是良辰丫,今天我们来学习一下线程池.线程池到底是什么呢?我们一起往下看💞💞💞
🧑个人主页:良辰针不戳
📖所属专栏:javaEE初阶
🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
💦期待大家三连,关注,点赞,收藏。
💌作者能力有限,可能也会出错,欢迎大家指正。
💞愿与君为伴,共探Java汪洋大海。
1. 什么是线程池
我们听说过字符串常量池,数据库连接池等,字符串常量池是放字符串常量的,那么,
线程池
,顾名思义,就是放线程的.
线程池的目的是提高效率,线程池的创建虽然比进程轻量,但是频繁创建线程的时候,也会有巨大的开销.那么我们就需要准备一个线程池,创建线程不是直接从系统申请,而是从线程池里面取,线程不用了的时候还给线程池.
线程提高效率方式.
- 协程,这是一种轻量级线程,但是java标准库还不支持.
- 线程池
为什么从线程池里面拿线程比从系统创建线程更高效???
从线程池拿线程,纯粹的用户态操作;而从系统创建线程,涉及到用户态和内核态之间的切换,真正的创建是要在内核态完成的.
一个操作系统 = 内核 + 配套的应用程序
- 内核是操作系统最核心的功能模块集合,硬件管理,各种驱动,进程管理,内存管理,文件系统.
- 内核需要给上层应用程序提供支持,应用程序调用系统内核,告诉内核,自己要进行某个操作,内核再通过驱动程序,操作显示器,完成上述功能.
- 应用程序,同一时刻有很多个,但是内核只有一个,内核要给那么多程序提供服务,有时候服务不一定那么及时.
线程池的优点:
- 降低资源消耗:通过重复利用已经创建的线程降低线程创建和销毁造成的消耗.
- 提高响应速度:当任务到达的时候,任务可以不需要等待线程创建就能立即执行.
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,而且会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控.
2. 标准库中的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test19 {
public static void main(String[] args) {
//下面的参数10为10个线程,下面并非直接new ExecutorService对象
//而是通过Executors类里面的静态方法完成对象的构造(工程模式)
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("线程1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
pool.submit(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("线程2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
}
下面为运行结果
上述创建线程池我们用到了工厂模式,简述一下
工厂模式
,工厂模式在创建对象的时候,不能直接new,而是通过一些其它的方法(通常是静态方法),协助我们把对象创建出来.说白了,工厂模式就是用拉力填构造方法的坑,如果想要提供多种不同的构造对象的方式,就要基于重载.
上述代码方法签名一样,无法构成重载,这是构造方法的局限性,而工厂模式就解决了一些这样的问题,在这里就不细说了.
3. 线程池的几个关键词
接下来我们来了解线程池的几个关键词.注意,查看官方文档是我们以后在工作过程中非常重要,我们要学着并且尝试看源码以及官方文档.
//1.核心线程数
corePoolSize
//2.最大线程数
maximumPoolSize
如果当前任务比较多,线程池就会创建一些"临时线程";如果当前任务少了,比较空闲了,线程池就会把多出来的临时线程销毁掉.为了便于理解,大家也可以把核心线程数想象成正式员工,把最大线程数想象成正式员工+临时员工.
long keepAliveTime
TimeUnit unit
任务少的时候,临时线程并不会立即销毁,而是有一个存活时间.
BlockKingQueue<Runnable> workQueue
线程池要管理很多的任务(线程就可以认为是任务),我们可以手动指定线程池一个队列,此时我们就可以非常方便的控制且获取队列中的信息了.submit方法就是把任务放到该队列中
//工厂模式,创建线程辅助的类
ThreadFactory threadFactory
//线程池的拒绝策略,如果线程池线程满了,继续添加任务,就会拒绝
RejectedExecutionHandler handler
4. 标准库提供的四种拒绝策略(面试题)
- 如果满了,继续添加任务,就会抛出异常.
- 添加的线程自己负责这个任务.
- 丢弃最老的任务.
- 丢弃最新的任务.
注意:
- 最老的认为是队列的队首元素,不执行了,直接删除.
- 线程池没有依赖阻塞行为,而是通过额外实现了其它逻辑更好的处理,阻塞有的时候可行,有的时候不好使,线程池中不希望依赖满了阻塞,而主要利用空了进行阻塞.
- ThreadPoolExecutor类的构造方法的参数,要重点去掌握.
5. 线程池的执行流程
- 当新加入一个任务时,先判断当前线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务;
- 如果结果为 true,则判断任务队列是否已满,如果结果为 false,则把任务添加到任务队列中等待线程执行.
- 如果结果为 true,则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务.
- 如果结果为 true,执行拒绝策略.
6. 手动实现线程池
- 核心操作为 submit, 将任务加入线程池中
- 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
- 使用一个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
- 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增
线程了.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
// 阻塞队列用来存放任务.
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
// 此处实现一个固定线程数的线程池.
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
try {
while (true) {
// 此处需要让线程内部有个 while 循环, 不停的取任务.
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class Test21 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 100; i++) {
//匿名内部类也要遵循变量捕获功能
int number = i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + number);
}
});
}
}
}
从运行结果大家可以发现,线程池任务的执行顺序和添加顺序不一定是相同的,因为这些线程是无序调用的.
实际工作中,线程池要创建多少线程呢?根据实际情况而定,比如CPU状况,物理逻辑等.
7. 总结多线程初阶
看到这里,多线程初阶就结束了,接下来我们总结几个面试常考的内容,希望小小的内容可以帮到大家.文章来源:https://www.toymoban.com/news/detail-417394.html
7.1 如何保证线程安全
- 使用没有共享资源的模型
- 共享资源只读,不写( 不需要写共享资源的模型使用不可变对象)
- 直面线程安全(保证原子性,顺序性,可见性)
7.2 线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
7.3 线程与进程的区别
-进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。文章来源地址https://www.toymoban.com/news/detail-417394.html
- 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
- 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
- 线程的创建、切换及终止效率更高。
到了这里,关于【多线程】线程池详解,常见的面试题,以及手动实现线程池的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!