1.Java线程状态
2.那在操作系统层面,线程状态的划分
Java把阻塞I/O设置到了Runnable状态中
3. 线程池核心参数
- corePoolSize 核心线程数,保留线程数,任务执行完也不会结束
- maximumPoolSize 最大线程数,核心线程数+救急线程
- KeepAliveTime 救急线程的生存时间
- unit 时间单位,常与timeout配合使用
- workQueue 阻塞队列
- threadFactory 线程工厂
- handler 拒绝策略
阻塞队列的类型: - SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
- LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致内存占用过多或OOM
- ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务
拒绝策略的类型:
- AbortPolicy:中断抛出异常
- DiscardPolicy:默默丢弃任务,不进行任何通知
- DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
- CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)
线程池复用的原理
-
为什么需要线程池?
每次创建线程都需要创建一个虚拟机栈,当线程运行结束,又需要销毁这部分内容,JVM数据区大小也是有限的,所以使用线程池,重复利用线程来达到节省资源的效果。 -
线程池工作原理
2.1 提交新任务后,当线程池中线程数小于核心线程数,新提交的任务将通过创建一个新线程来执行,即使此时线程池中存在空闲线程,也会先创建新的线程来执行
2.2 核心线程数满了,新的任务会放入阻塞队列
2.3 阻塞队列都满了,就会创建救急线程来执行
2.4救急线程也满了,就会执行拒绝策略 -
线程池底层实现原理
3.1通过查看ThreadPoolExecutor类,内部维护一个AtomicInteger类变量ctl,它用于表示线程池的状态和线程数,其中前三位表示线程池状态,后29位表示线程池中线程数,初始化线程池状态为RUNNING,数量为0. 通过一些内部方法可以获取和设置线程池状态和线程数
3.2看execute(Runnable command)方法
源码将线程和任务封装到了Worker中,然后将Worker添加到HashSet集合中,添加成功后通过调用线程的start()来执行任务,
在封装的执行线程方法runWorker()中,循环从阻塞队列中不断获取任务,通过getTask()方法来获取。
sleep()和 wait()方法比较
1.共同点:
都是用来让线程进入阻塞状态,放弃CPU占用
2.不同点:
2.1 方法归属:sleep()是Thread类的一个静态方法
wait()是Object类的方法,每个对象都可以
2.2 醒来的时机: sleep()和wait()都可以传入时间参数,一段时间后自动醒来
sleep()必须传入时间参数
wait()如果不传入,将会一直阻塞,可以使用notify(),notifyAll()来唤醒
都可以被打断唤醒
2.3 作用范围:sleep()是在任何时候都可以使用,线程睡眠时如果有锁,并不会释放锁
wait()只有配合synchronized在锁代码快中使用,会释放锁
Lock和synchronized
-
语法层面:
- synchronized是关键字,源码在JVM中,用c➕➕实现
- Lock是接口,使用的API层面,java语言实现
- synchronized执行完会自动释放锁,Lock需要手动调用unlock()来释放锁
-
功能层面
- 两者都属于悲观锁,线程互斥,同步,锁重入
- lock提供了壁synchronized更全面的功能,获取等待状态,公平锁,可打断,可超时,多条件变量(synchroized 中调用wait()时,线程进入waitSet队列等待只有一个队列,而例如ReentrantLock提过多个waitSet(condition)来实现不同的等待队列)
-
性能层面
- 在没有竞争,synchronized提供了锁升级策略,性能还行
- 锁竞争激烈时,Lock的实现通常会提供更好的性能