ThreadLocal
- 简介
ThreadLocal类可以让每个线程绑定自己的值(专属变量),即一个本地线程副本变量的工具类。 - 工作原理
通过以下Thread的源码中可知,在Thread类中就有一个threadLocals和一个inheritableThreadLcals变量,它们都是ThreadLocalMap类型的变量(可以理解为由ThreadLocal定义的一个HashMap类型的变量)。默认情况下,这两个变量都为null,只有当调用ThreadLocal类的set方法时才会创建它们。 ThreadLocal中的kkey就是ThreadLocal本身,value存的是每个线程变量的值,ThreadLocal也可以理解为是ThreadLocalMap的封装,传递了变量值,可以避免线程之间的竞争。ThreadLocalMap为ThreadLocal的静态内部类,调用set、get方法时其实是调用ThreadLocalMap的方法。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- ThreadLocal内存泄露问题
ThreadLocal中的ThreadLocalMap的key是弱引用,value是强引用,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候key会被清理掉,但是value不会被清理掉,这就造成了value可能无法被gc回收,即为内存泄漏,所以使用完ThreadLocal后最好手动调用remove方法。
线程池
简介
java线程池解决了在高并发情况下大量的新建和销毁线程会带来效率低下的问题。先预先创建一些线程放在这里,管理起来,形成一个线程池。
- 为什么使用线程池
1.提高响应速度。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.降低资源消耗。任务到达时可以不需要等创建线程就能立即执行
3.提高线程的客观理性。线程是稀缺资源,如果创建太多,会消耗系统资源,还会降低系统稳定性,使用线程池可以统一进行对线程的分配、调优和监控。
线程池的使用场景
1.高并发场景:如tomcat的处理机制,内置了线程池处理http请求;
2.异步处理任务:如当需要做一个业务逻辑中带短信通知的需求,短信和业务逻辑的耦合不是特别强时,可以用spring的异步方法改造,增加@Asyn注解定义一个线程池。
如何创建线程池
1.通过Executors工具类去创建
这种方法的内部实际上都是调用了ThreadPoolExecutor的构造方法。通过new这种方法可以创建四种线程池:
newScheduledThreadPool:创建一个固定大小的线程池,线程池内线程空闲等待时间无限长,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,新任务会进入
DelayedWorkQueue(一个使用优先级队列实现的无界阻塞队列)中,按照超时时间排序的队列结构。适用于周期性执行任务的场景。
newSingleThreadScheduledExecutor:创建一个FinalizableDelegatedExecutorService包装的只有一个新城的线程池,空闲等待时间无限长,当线程繁忙时新任务进入DelayedWorkQueue(一个使用优先级队列实现的无界阻塞队列)中(底层调用的还是ScheduledThreadPoolExecutor的构造方法)。**适用于一个任务一个任务的场景。**可缓存频繁要用到的线程。
newCachedThreadPool:创建一个核心线程数为0,最大线程数为最大,空闲等待时间为60s的线程池。因为核心线程数为0,所有当新任务到来会进入SynchronousQueue同步队列中,但最大线程为最大的Integer,所以可以创建线程。适用于执行很多短期异步的小程序或者负载较轻的服务器。
newFixedThreadPool:创建一个自定义核心线程数和最大线程数的、空闲等待时间为0(不限时),池子满了时新任务进入无界的阻塞队列LinkedBlockingQueue中。适用于长期执行的任务。
2.通过ThreadPoolExecutor类的3种构造方法来创建线程池
构造函数参数分析:
- 最重要的三个参数
corePoolSize:核心线程数。最小可同时运行的线程数量。
maximumPoolSize:最大线程数。最多可同时运行的线程数量。
workQueue:阻塞队列。新来任务时会判断当前运行的线程数量是否达到核心线程数,若达到,新任务就会被存放在阻塞队列种。 - 其他常见参数
keepAliveTime:空闲等待时间。核心线程数以外的线程空闲时等待的时间,超过则销毁线程。
unit:空闲等待时间的单位。
threadFactory:创建新线程的线程工厂。
handler:拒绝策略。超出最大线程数时还有新任务进来的拒绝策略。
四种拒绝策略
- ThreadPoolExecutor.AbortPolicy:抛出rejectedExecutionException来绝新任务的处理。
- ThreadPoolExecutor.CallerRunsPolicy:将任务分配给调用线程来执行。
- ThreadPoolExecutor.DiscardPolicy:直接丢弃。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃最早未处理的任务。
线程池流程
任务添加时会先把核心线程用完,如果超过就会进入阻塞队列种。如果阻塞队列满了,就会判断是否大于最大线程数,如果超过则用拒绝策略进行处理。否则,会新建线程来执行任务值到等于最大线程数。如下图所示。