1.进行和线程
什么是进程:进程是内存分配的基本单位,它是程序执行时的一个实例,会被放到进程就绪队列,等进程调度器选择它,给它时间片,它才会运行。在java中启动进程,main,test,springboot,java-jar,都是启动一个进程。
什么是线程:线程是程序执行时的最小单位,进程由多个线程组成,线程间共享进程所有资源,每个线程有自己的栈和变量。多cpu运行多个线程同时运行,多线程可以实现并发操作,在web中每个请求分配一个线程来处理。每个请求一个线程
2.java使用线程常用四种方式
方式一:继承Tread,重写run方法,new Tread实例对象,start方法(严谨说法)
方式二:创建runnable接口实现类,覆写run方法,作为参数传入tread类,进行有参构造,调用start
方法三:实现callable,传给FutureTask
我记得好像还有用CompletableFuture,创建异步线程?
CompletableFuture的supplyAsync方法,提供了异步执行的功能,线程池也不用单独创建了。实际上,它CompletableFuture使用了默认线程池是ForkJoinPool.commonPool。
方法四:调用线程池
ps:
main是一个线程,在main中new一个线程,相当于异步执行
new线程的时候并没有创建线程,而是调用start()以后,才会帮我们创建线程,并执行线程中的run方法。
调用start方法以后。线程并不是立刻执行,只有当cpu时间片轮换到它以后,线程才会被执行。
2.0.1调用start()以后,会去执行run()方法,为什么我们不直接调用run()?
star和run方法的区别
直接调run方法,在当前线程中同步执行该方法,就不是异步执行方法了。
start是另开一个线程,run方法是在本线程中同步执行该方法。
2.0.2如何区分这些线程,通过线程名称区分,线程名称默认Tread-x。你也可以自己取一个名字,可取可不取。tread.setName(“zzzz”)
2.1 实现runable方法,实现run方法,创建Tread实例对象,传入参数。
方法一和方法二的区别?
继承,实现,单继承,继承别的类,不可以。多实现,一个类可以实现多个接口,还可以继承某一个类。方法二更灵活。使用start以后都是调用都是run方法,调用重写过的run方法。
那为什么方法二调用的是runable的run方法不是tread的run方法?
同时tread 的 init方法的时候,把target传了过去,因为tread没有重写,所以target不为空,所以调用target的run方法。
匿名内部类也可以new tread,或者lamda表达式也可以。
2.3实现callable,callable可以有返回值,返回值通过FutureTask进行封装。类上的泛型就是我们返回值的类型。没有获取返回值是异步,获取返回值就变成同步了,还是异步吗?算的上异步,因为是在主线程中获取值,其实在另外一个线程中,早就跑完了,只是获取值而已。在它获取到值之前,都是异步的。
2.4线程池
创建就两种,具体写的方式四种就是上面四种。
3线程的生命周期
ps:vue的钩子函数,vue生命周期,vue的钩子函数不等于vue生命周期,正在vue生命周期中,自动触发的八个钩子函数
3.1生命周期图
3.2线程的方法
new新建状态,Rybbavl就绪状态,等待时间片,running运行中状态,run方法结束,线程销毁(死亡状态)。
3.3线程的状态
新建状态,就绪状态,运行状态,阻塞状态(睡眠或者挂起),死亡状态
3.4线程方法
yled:执行变就绪,让出cpu,不会释放锁(线程锁)
sleep:执行的线程睡眠,时间到,返回到可运行状态,会让出cpu,不会放弃锁资源。
join:通常用在主线程中,等待其他线程完成再结束main主线,会让出cpu,不会放弃锁资源
wait:强制当前正在执行的线程等待,直到被唤醒,返回可执行状态。让出cpu,会放弃锁资源。(通过notifu或者notifyall才会激活)
deamon:守护线程
除了wait,会让锁,其他都不让锁。
3.5线程通信
notify,notifyall,wait,通过某种机制交互,等待通知就是之一。
还有其他的通信方式哈
notify:只通知一个,随机选一个,多选一
notifyall:对象上所以线程全部被唤醒
4.线程池
为什么要使用线程池?
假如100万请求,创建100次线程,创建线程过多,导致内存不足,服务器崩溃。
1.线程池里面1000个,一批批获取,服务器处理只有1000个线程,避免高并发,防止服务器过载。
2.创建销毁,频繁创建销毁,费时间费资源。线程池是提前创建好的线程,只需要分配,不需要销毁,放回线程池就可以了。对线程的复用,避免创建销毁线程。
3.管理线程生命周期
用户态和内核态(我也不咋懂)
创建和销毁涉及底层,就是内核态
4.1线程池的原理
七大参数
corePoolSize:核心线程数
workque:工作队列,超过核心线程数,任务进行队列排队
-
SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务;
-
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
-
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
MaximumPoolSize:最大线程数(核心+非核心),非核心线程数用完之后达到空闲时间会被销毁。
Hander:拒绝策略,任务超过最大线程数+队列排队数,多出来的任务取决于Hander
KeepAliveTime: 非核心线程的最大空闲时间,到了这个空闲时间没被使用,非核心线程销毁
Unit: 空闲时间单位
ThreadFactory:使用ThreadFactory创建新线程。 推荐使用Executors.defaultThreadFactory
4个策略:
-
AbortPolicy丢弃任务并抛出RejectedExecutionException异常;
-
DiscardPolicy丢弃任务,但是不抛出异常;
-
DiscardOldestPolicy丢弃队列最前面的任务,然后重新尝试执行任务;
-
CallerRunsPolicy由调用线程处理该任务
4.2线程池使用流程,原理
4.3线程池的使用
Execuertors提供哪些方法创建线程池?
java里面有哪些常用线程池?
java有哪些线程池?
EXcutors提供的静态方法创建的
常用6种
一个单线程的线程池 newSingleThreadExecutor
最大线程数和核心线程数都是1,线程池中的线程不会被销毁
这个工作队列最大值就是Integer最大值,该工作队列遵循先进先出
一个固定长度的线程池 newCachedThreadPool
这个线程池中,都是核心线程,没有非核心线程,传入是几,核心和非核心都是一样。不会被销毁,工作队列大小没有限制,先进先出。
适用于长期任务,性能好很多,放入工作队列慢慢执行。
一个可以无限扩大(缓存)的线程池 newFixedThreadPool(int n)
没有核心线程,最大线程数是Integer的最大值,超过60s没有执行任务,就会销毁。sync:这个队列不保存任务,有新任务来,直接新建线程来执行。新任务到来时,有空闲就复用,没有就新建。执行短期异步的小程序或者负载轻的服务器。
带定时任务的线程池,框架底层会用到 ScheduledThreadPool
springboot中的schedule 底层就是springtask,启动类大注解,就可以开发spring task,在某一个定时任务的方法上打注解,定时执行。
固定速率和固定延迟时间有区别,一个不管线程内部任务,一个是任务结束时增加。
拥有多个任务队列(可窃取的线程池)的线程池 newWorkStealingPool(n)
工作窃取,先干完活,又帮别人干任务,从队列尾部来窃取。
Excutors自定义线程池
5.ThreadPoolExecutor重要方法
Execute 执行任务
submit 执行任务
shutdown 等任务执行完后才终止
shotduwnNow 立即执行,尝试打断任务
isTerminated 是否终止
6.线程池中的最大线程数
线程池中的数,有一个大概算法,cpu密集型应用,线程池大小设置为N+1,如果是io密集型:操作文件,线程池大小设置为2N+1.
io优化中,这样的估算公式可能更加合适,最佳线程数目 = 。。
线程等待时间占比例高,需要越多线程,根据上面预估一个值,然后配合测试,得到最佳线程数目。