app做好了网站怎么做,免费创建社区论坛网站,传媒大学附近网站建设公司,贵金属网站模板目录
一、线程池的由来 二、线程池的简单介绍
1、ThreadPoolExecutor类
#xff08;1#xff09;核心线程数和最大线程数#xff1a;
#xff08;2#xff09;保持存活时间和存活时间的单位
#xff08;3#xff09;放任务的队列
#xff08;4#xff09;线程工…目录
一、线程池的由来 二、线程池的简单介绍
1、ThreadPoolExecutor类
1核心线程数和最大线程数
2保持存活时间和存活时间的单位
3放任务的队列
4线程工厂
5拒绝策略
2、Executors类
3、线程池的执行流程
4、讨论线程池中创建多少线程合适 三、线程池的模拟实现
1阻塞队列存放要执行的任务
2submit方法添加任务的方法任务添加到队列中
3构造方法指定创建多少个线程线程在这个构造方法中都创建好了
4存放线程的链表每创建一个线程都放进链表中这样也能让我们找到某个线程
5最终代码 测试用例
都看到这了点个赞再走吧谢谢谢谢谢 一、线程池的由来
最开始进程可以解决并发编程的问题但是这个代价太大了于是引入了 “轻量级进程” 线程
线程也能解决并发编程的问题而且线程的开销比进程要小的多但是线程如果太多了创建销毁线程的频率进一步提高此时的线程创建销毁的开销就不能忽视了。
为了解决上述问题大佬们给出了两个解决方案
1引入 “轻量级线程”纤程 / 协程 协程的本质是程序猿在用户态代码中进行调度不是靠内核的调度器调度的这样就节省了很多开销协程是在用户代码中基于线程封装出来的可能是N个协程对应1个线程也可能是N个协程对应M个线程。
2引入 “线程池” 线程池的概念创建一个线程这个线程执行完不会把这个线程给销毁而是把这个线程放到线程池中当我们需要用这个线程的时候再从线程池中拿不需要的时候就放在线程池中并不会销毁它这样就省去了频繁的创建销毁线程了。
为啥从线程池中取线程 比 从系统中申请线程的创建更高效呢 举个栗子
假设在银行场景中滑稽老铁要去这个银行办理一个业务一般银行中大堂有复印机这时滑稽老铁没有带身份证复印件此时滑稽老铁要去搞到身份证复印件有两个选择其一选择把身份证给柜员让柜员帮滑稽老铁复印但是这个操作是不可控的可能这个柜员中途被老板安排了其他活那这个时候就不能帮滑稽老铁复印身份证了要等忙完老板安排的活再帮滑稽老铁复印身份证其二选择滑稽老铁自己去大堂中复印身份证这样就比较可控了滑稽老铁可以很快的去到打印机立马复印出来再去办理他的业务。如图 这里的大堂就是用户态柜台就是内核态从线程池中取线程是纯用户态代码可控 通过系统申请创建线程需要内核完成不可控 二、线程池的简单介绍
1、ThreadPoolExecutor类
ThreadPoolExecutor参数最多的构造方法明白了这个构造方法其他构造方法的参数也就都明白了如图 1核心线程数和最大线程数 corePoolSize核心线程数正式员工线程 maximumPoolSize最大线程数正式员工线程 实习员工线程 举个栗子一个公司中有10个正式员工这10个正式员工是不能随便开除的当这10个正式员工忙不过来的时候公司为了降低成本会招聘实习员工而这几个实习员工是可以随便开除的当公司稳定一段时间不忙后就会开除几个实习员工。 2保持存活时间和存活时间的单位 KeepAliveTime保持存活时间实习生线程允许摸鱼的最大时间 unit存活时间的单位可以是hour 、 min 、 s 、 ms 3放任务的队列 和定时器类似线程池中也可以持有多个任务要执行的任务使用Runnable来描述任务。 4线程工厂 通过这个工厂类创建线程对象Thread对象工厂类里面有方法封装了new Thread的操作同时给Thread设置了一些属性我们想要创建线程的时候可以直接使用工厂类的方法创建。 举个栗子 描述一个点可以用二维坐标和极坐标来表示二维坐标xy 极坐标rα 这里通过new一个类来得到一个点这个类里有两个构造方法参数分别是double xdouble ydouble rdouble α那么这两个构造方法的参数类型都一样构成不了重载如图 那我们就改方法名不就好了在使用static修饰通过不同的方法名获取类在方法里new一个类里面设置一些参数再返回这个类如图 这样的的类就称为工厂类工厂类里面得到类的方法就称为工厂方法。 总的来说通过静态方法new了一个对象在这个静态方法设置不同的属性构造对象的过程就称为工厂模式。 5拒绝策略 在线程池中有一个阻塞队列这个队列容纳线程有上限如果这个任务队列满了这时有往再添加任务会发生啥事 这就引出了拒绝策略在线程池中会有四个拒绝策略如图 第一个策略会直接抛出一个异常这样旧的任务执行不了新的任务也执行不了 第二个策略把新的任务丢给添加任务队列的线程执行不给入队列同时旧的任务依然在执行 第三个策略把最旧的任务丢弃添加最新的任务进来 第四个策略直接把新的任务丢弃了不执行新的任务旧的任务会继续执行 2、Executors类
ThreadPoolExecutor类本身使用起来比较复杂标准库给我们提供了另一个版本把ThreadPoolExecutor封装了一下这个类就是Executor类通过这个类创建出不同的线程池对象在其内部已经把ThreadPoolExecutor创建好了并且设置了一些参数。
Executor的简单使用其中主要方法有一下4个如图 我们创建一个固定线程数目的线程池再往里添加任务 代码 public class ThreadPoolTest {public static void main(String[] args) {ExecutorService service Executors.newFixedThreadPool(4);service.submit(new Runnable() {Overridepublic void run() {System.out.println(hello word);}});}
} 执行结果 那啥时候使用Executor啥时候使用ThreadPoolExecutor呢
网上流传了 阿里巴巴java开发编程规范里面写了不建议使用Executor而且一定要使用ThreadPoolExecutor里面说用ThreadPoolExecutor意味着一切都在掌控之中可以避免一些不必要的因素我们可以作为参考不必奉为金科玉律他们两各有各的优缺点这也要以以后入职的公司编程规范为准。
3、线程池的执行流程 1当有有任务要让线程池里面的线程执行时会比较工作线程数和核心线程数 如果工作线程数 核心线程数则会直接安排线程去执行这个任务。 2当工作线程数 核心线程数即线程池中的核心线程数满了会添加进阻塞任务队列中天气任务队列前也会判断任务队列是不是空是空就阻塞等待。 3如果线程池中的存活线程数 核心线程数并且阻塞任务队列也满了此时会判断是否到了最大线程数maximumPoolSize如果没有到达就会让非核心线程去执行这个任务。 4如果当前线程数到达了最大线程数则会执行拒绝策略。 4、讨论线程池中创建多少线程合适
假设一个进程中所有线程都是cpu密集型这时每个线程的工作都是在cpu上执行的此时线程池中的数目就不应该超过Ncpu的逻辑核心线程数
如果一个进程中所有线程都是IO密集型的这时每个线程的大部分工作都是在等待IO此时线程池中的数目就可以远远超过Ncpu的逻辑核心线程数
上述情况都是极端情况实际上一个进程中的线程有cpu密集型的也有IO密集型的只是比例不同。由于程序的复杂性很难直接对线程池进行预估更准确的做法是通过实验 / 测试的方法找出合适的线程数目也就是尝试给线程池设定不同的线程对不同线程情况线程池执行的效率、性能进行评估找到合适的线程数目。 三、线程池的模拟实现
模拟线程数目固定的线程池
1阻塞队列存放要执行的任务
代码
//阻塞队列存放要执行的任务
private BlockingQueueRunnable queue new ArrayBlockingQueue(5);
2submit方法添加任务的方法任务添加到队列中
代码
//提供submit方法可以添加任务
public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);
}
3构造方法指定创建多少个线程线程在这个构造方法中都创建好了
public MyThreadPoolExecutor(int n) {for (int i 0; i n; i) {Thread t new Thread(() - {while (true) {try {//取出一个任务Runnable runnable queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();list.add(t);}}
解析线程里面取出一个任务就执行这个任务如果队列里没有任务就会阻塞等待等有任务再执行任务如此循环往复每创建一个线程都要放进链表中也要记得start。
4存放线程的链表每创建一个线程都放进链表中这样也能让我们找到某个线程
代码
//存放线程的链表
ListThread list new ArrayList();
5最终代码 测试用例
class MyThreadPoolExecutor {//存放线程的链表ListThread list new ArrayList();//阻塞队列存放要执行的任务private BlockingQueueRunnable queue new ArrayBlockingQueue(5);//提供submit方法可以添加任务public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}public MyThreadPoolExecutor(int n) {for (int i 0; i n; i) {Thread t new Thread(() - {while (true) {try {//取出一个任务Runnable runnable queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();list.add(t);}}
}
public class MyThreadPoolExecutorTest {public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor myThreadPoolExecutor new MyThreadPoolExecutor(4);for (int i 0; i 1000; i) {//变量捕获int n i;myThreadPoolExecutor.submit(new Runnable() {Overridepublic void run() {System.out.println(执行任务: n 当前线程 Thread.currentThread().getName());}});}}
}测试用例指定线程池的数目为4个线程添加1000次任务到阻塞队列中让着4个线程从阻塞队列中拿任务再执行任务任务打印0~1000并显示是哪个线程打印的
注意这里我们打印那里我们不能直接放 i 这里涉及到变量捕获不能编译通过但他们可以在循环里创建一个变量把 i 的值赋值给这个变量再打印 n这样每循环一次都会创建一个成员变量这个成员变量也不会变预期也和我们想要预期效果一样。
执行结果如图 可以看到并不是顺序打印1~1000的因为不同线程拿到任务的时机不同多线程执行的顺序也是随机的。 都看到这了点个赞再走吧谢谢谢谢谢