1、线程池的原理,为什么要创建线程池?创建线程池的方式;
优点:
1.线程是稀缺资源,使用线程池可以有效减少创建和销毁线程次数,每个工作线程都可以重复使用。
2.可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
线程池的创建:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
handler:超出线程范围和队列容量的任务的处理程序
实现原理:
提交一个任务到线程池中,线程池的处理流程如下:
1.判断**线程池里的核心线程**是否都在执行任务,
如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。
如果核心线程都在执行任务,则进入下个流程。
2.线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。
如果工作队列满了,则进入下个流程。
3.判断**线程池里的线程**是否都处于工作状态,
如果没有,则创建一个新的工作线程来执行任务。
如果已经满了,则交给饱和策略来处理这个任务。
饱和策略:
当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。
这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。
JAVA提供了4中策略:
1、AbortPolicy:直接抛出异常
2、CallerRunsPolicy:只用调用所在的线程运行任务
3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。
2、volatile、ThreadLocal的使用场景和原理;
**volatile原理**
volatile变量进行写操作时,JVM 会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓存行的数据写会到系统内存
Lock 前缀指令实际上相当于一个内存屏障(也成内存栅栏),
它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
即在执行到内 存屏障这句指令时,在它前面的操作已经全部完成。

volatile的适用场景
1)状态标志,如:初始化或请求停机
2)一次性安全发布,如:单列模式
3)独立观察,如:定期更新某个值
4)“volatile bean” 模式
5) 开销较低的“读-写锁”策略,如:计数器
ThreadLocal原理
ThreadLocal是用来维护本线程的变量的,并不能解决共享变量的并发问题。
ThreadLocal是 各线程将值存入该线程的map中,以ThreadLocal自身作为key,需要用时获得的是该线程之前 存入的值。
如果存入的是共享变量,那取出的也是共享变量,并发问题还是存在的。
ThreadLocal是什么、有什么、能做什么?
ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。
说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。
**ThreadLocal提供的方法**

> **对于ThreadLocal而言,常用的方法,就是get/set/initialValue方法。**
**我们先来看一个例子**
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ThreadLocalTest {
public static A a = new A();
public static final ThreadLocal<A> threadLocal = new ThreadLocal<A>(){
@Override
protected A initialValue() {
return a;
}
};
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++){
threads[i] = new Thread(()->{
threadLocal.get().setNumber(threadLocal.get().getNumber() + 5);
log.info("" + threadLocal.get().getNumber());
}, "Thread" + (i + 1));
}
for (Thread thread : threads){
thread.start();
}
}
* Thread3 INFO - 15
* Thread5 INFO - 25
* Thread4 INFO - 20
* Thread2 INFO - 10
* Thread1 INFO - 5
}
class A {
private int number = 0;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
> **很显然,在这里,并没有通过ThreadLocal达到线程隔离的机制,
可是ThreadLocal不是保证线程安全的么?这是什么鬼?**
>
> **虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,
但是如果这个局部变量都指向同一个对象呢?
这个时候ThreadLocal就失效了。
仔细观察下图中的代码,
你会发现,threadLocal在初始化时返回的都是同一个对象a!**
看一看ThreadLocal源码
**我们直接看最常用的set操作:**
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
> **你会看到,set需要首先获得当前线程对象Thread;**
>
> **然后取出当前线程对象的成员变量ThreadLocalMap;**
>
> **如果ThreadLocalMap存在,那么进行KEY/VALUE设置,KEY就是ThreadLocal;**
>
> **如果ThreadLocalMap没有,那么创建一个;**
>
> **说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值。**
**看一下get操作:**
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();
}
> **这里其实揭示了ThreadLocalMap里面的数据存储结构,
从上面的代码来看,
ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。**
**ThreadLocalMap.Entry:**
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
> **在JAVA里面,存在强引用、弱引用、软引用、虚引用。这里主要谈一下强引用和弱引用。**
>
> 强引用,就不必说了,类似于:A a = new A(); B b = new B();
考虑这样的情况:C c = new C(b);
> **b = null;
> **
> 考虑下GC的情况。要知道b被置为null,那么是否意味着一段时间后GC工作可以回收b所分配的内存空间呢?
答案是否定的,
因为即便b被置为null,但是c仍然持有对b的引用,而且还是强引用,
所以GC不会回收b原先所分配的空间!
既不能回收利用,又不能使用,这就造成了**内存泄露**。
>
> 那么如何处理呢?
> **可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)**
分析到这里,我们可以得到:

**这里我们思考一个问题:ThreadLocal使用到了弱引用,是否意味着不会存在内存泄露呢?**
> **首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,
只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。
但是value却存在一条从Current Thread过来的强引用链。
因此只有当Current Thread销毁时,value才能得到释放。**
>
> **因此,只要这个线程对象被gc回收,就不会出现内存泄露,
但在threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露。
最要命的是线程对象不被回收的情况,
比如使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。**
>
> **那么如何有效的避免呢?**
>
> **事实上,在ThreadLocalMap中的set/getEntry方法中,
会对key为null(也即是ThreadLocal为null)进行判断,
如果为null的话,那么是会对value置为null的。
我们也可以通过调用ThreadLocal的remove方法进行释放!**

ThreadLocal的适用场景
场景:数据库连接、Session管理
3、ThreadLocal什么时候会出现OOM的情况?为什么?
ThreadLocal变量是维护在Thread内部的,这样的话只要我们的线程不退出,对象的引用就会 一直存在。
当线程退出时,Thread类会进行一些清理工作,其中就包含ThreadLocalMap,
Thread调用exit方法如下:
private void exit() {
if (group != null) {
group.threadTerminated(this)
group = null
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null
/* Speed the release of some of these resources */
threadLocals = null
inheritableThreadLocals = null
inheritedAccessControlContext = null
blocker = null
uncaughtExceptionHandler = null
}
ThreadLocal在没有线程池使用的情况下,正常情况下不会存在内存泄露,
但是如果使用了线程 池的话,就依赖于线程池的实现,
如果线程池不销毁线程的话,那么就会存在内存泄露。
ThreadLocal源码分析