目录
1、线程池
了解线程池使用的几种队列(线程池的三种队列区别)
常用的四种创建方法
ThreadPoolExecutor拒绝策略
ThreadPoolExecutor扩展
2、ThreadLocal
3、CountDownLatch计数器
池化思想主要是为了避免不停地创建和销毁应用(线程、数据库连接)影响系统性能。
创建线程池实际上还是利用 ThreadPoolExecutor
类实现的
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler)
这几个核心参数的作用:corePoolSize
为线程池的基本大小。maximumPoolSize
为线程池最大线程大小keepAliveTime
和 unit
则是线程空闲后的存活时间。workQueue
用于存放任务的阻塞队列。handler
当队列和最大线程池都满了之后的饱和策略。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
Executors.newCachedThreadPool() 线程池数量不确定,有空闲线程则复用,没有就创建新的
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
当队列已经满了,线程池已经是最大数量并且线程都用完了,此时若还有任务进来,需要有拒绝策略。jdk内置了四种拒绝策略:
此外,我们可以通过实现RejectedExecutionHandler接口自定义拒绝策略。
它提供了beforeExecute()、afterExecute()、terminated()等接口方法对线程池进行执行前、执行完成、线程池退出的控制。
强烈推荐:线程池面试题总结
拒绝策略详解
用来存储线程的本地变量,有一个内部类ThreadLocalMap,每个线程都有自己的ThreadLocalMap,key=ThreadLocal实例本身,value=线程局部变量缓存值
ThreadLocal内存泄露?
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用,系统GC时这个ThreadLocal就会被回收,ThreadLocalMap中就出现了key为null的Entry,如果线程不结束,这些Entry的value就会存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,造成内存泄漏。
ThreadLocalMap针对此做了一些优化,比如在调用ThreadLocal的get()、set()、remove()时都会清理ThreadLocalMap中所有key为null的value。但不能避免内存泄漏的发生,比如分配使用了ThreadLocalMap后不再调用get()、set()、remove()方法。
既然是ThreadLocal的弱引用导致了内存泄漏,那为什么不使用强引用?
使用强引用的话,如果引用ThreadLocal的对象已经回收了,但ThreadLocalMap还持有对ThreadLocal的强引用,导致ThreadLocal不能被回收,会产生更严重的内存泄漏。
避免内存泄漏?
每次使用完ThreadLocal,调用remove()进行清理。
ThreadLocalMap
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
引申:InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量。
是一个计数器,等其他线程执行完后再执行,计数器的初始值通过构造函数设置(new CountDownLatch(线程数量)),每当一个线程执行完计数器的值就-1,当计数器的值为0时表示所有线程执行完毕,主线程继续执行。包含三个重要方法:
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
CountDownLatch是通过继承AQS(AbstractQueuedSynchronizer)来实现的,AbstractQueuedSynchronizer学习