本文回复以下题目,文内大概有遗漏、错误或表达不敷清楚的地方。
- ThreadLocal
① 相识ThreadLocal利用场景和内部实现
② 利用ThreadLocal时要注意什么?比如说内存走漏?
- ThreadPoolExecutor
① 相识线程池的工作原理以及几个紧张参数的设置
② 往线程池里提交一个任务会发生什么?
③ 线程池的非核心线程什么时间会被开释?
④ 怎样排查死锁?
ThreadLocal
① 相识ThreadLocal利用场景和内部实现
利用场景:全局参数通报、解决线程安全题目
利用案例: PageHelper、MDC类、@Transactional的数据库毗连
ThreadLocal对象可以提供线程局部变量,每个线程Thread拥有一份自己的副本变量,多个线程间互不干扰。一个线程可以创建多个ThreadLocal对象,将其存到当火线程的ThreadLocalMap里。ThreadLocalMap底层数据布局是一个Entry数组,它的Entry是继承WeakReference的,key是ThreadLocal对象(弱引用)
● 为什么ThreadLocalMap中把ThreadLocal对象存储为Key时利用的是弱引用
一样寻常来说利用ThreadLocal时会有两个引用指向ThreadLocal对象,一个是创建ThreadLocal对象时的显式的引用,尚有一个就是ThreadLocalMap对ThreadLocal对象的弱引用,当我们不再利用ThreadLocal时,显式引用不再指向ThreadLocal对象。这时只有ThreadLocalMap对ThreadLocal对象的弱引用存在。
● 假设ThreadLocalMap对ThreadLocal的引用是强引用
由于ThreadLocalMap是属于线程的,而我们创建多线程时一样寻常是利用线程池举行创建,线程池中的部分线程在任务竣事后是不会关闭的,那么这部分线程中的ThreadLocalMap将会一直持有对ThreadLocal对象的强引用,导致ThreadLocal对象无法被垃圾接纳,从而造成内存走漏。
● 设置成弱引用之后
下一次垃圾接纳时,无论内存空间是否富足,只被弱引用指向的对象都会被直接接纳。以是将ThreadLocalMap对ThreadLocal对象的引用设置成弱引用,就能克制ThreadLocal对象无法接纳导致内存走漏的题目。但是ThreadLocalMap对value的引用是强引用,以是value部分还是有内存走漏的大概。以是ThreadLocal类定义了expungeStaleEntry方法用于清算key为null的value。expungeStaleEntry在remove中方法中调用。
② 利用ThreadLocal时要注意什么?比如说内存走漏?
ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key会导致内存走漏。
大概发送内存走漏的地方:
1、大量地(静态)初始化ThreadLocal实例,初始化之后不再调用get()、set()、remove()方法。
2、初始化了大量的ThreadLocal,这些ThreadLocal中存放了容量大的Value,而且利用了这些ThreadLocal实例的线程一直处于生动的状态。
最佳实践:
每次利用完ThreadLocal实例,都调用它的remove()方法,扫除Entry中的数据
用remove()方法最佳时机是线程运行竣事之前的finally代码块中调用
同时只管克制利用ThreadLocal存储大对象
ThreadPoolExecutor
① 相识线程池的工作原理以及几个紧张参数的设置
// 以ThreadPoolExecutor参数最全的构造器为例 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { }
② 往线程池里提交一个任务会发生什么?
③ 线程池的非核心线程什么时间会被开释?
壅闭队列里没有任务时,非核心线程要在比及keepAliveTime时间后才会开释
④ 怎样排查死锁?
起首清楚线程状态,一个Thread对象可以有多个状态,在java.lang.Thread.State中,统共定义六种状态:
1、NEW 线程刚刚被创建,也就是已经new过了,但是还没有调用start()方法,jstack下令不会列出处于此状态的线程信息 2、RUNNABLE #java.lang.Thread.State: RUNNABLE RUNNABLE这个名字很具有诱骗性,很轻易让人误以为处于这个状态的线程正在运行。究竟上,这个状态只是表现,线程是可运行的。我们已经无数次提到过,一个单核CPU在同一时候,只能运行一个线程。 3、BLOCKED # java.lang.Thread.State: BLOCKED (on object monitor) 线程处于壅闭状态,正在期待一个monitor lock。通常情况下,是由于本线程与其他线程公用了一个锁。其他在线程正在利用这个锁进入某个synchronized同步方法块大概方法,而本线程进入这个同步代码块也须要这个锁,终极导致本线程处于壅闭状态。 4、WAITING 期待状态,调用以下方法大概会导致一个线程处于期待状态: Object.wait 不指定超时时间 # java.lang.Thread.State: WAITING (on object monitor) Thread.join with no timeout LockSupport.park #java.lang.Thread.State: WAITING (parking) 比方:对于wait()方法,一个线程处于期待状态,通常是在期待其他线程完成某个操纵。本线程调用某个对象的wait()方法,其他线程处于完成之后,调用同一个对象的notify大概notifyAll()方法。Object.wait()方法只可以或许在同步代码块中调用。调用了wait()方法后,会开释锁。 5、TIMED_WAITING 线程期待指定的时间,对于以下方法的调用,大概会导致线程处于这个状态: Thread.sleep #java.lang.Thread.State: TIMED_WAITING (sleeping) Object.wait 指定超时时间 #java.lang.Thread.State: TIMED_WAITING (on object monitor) Thread.join with timeout LockSupport.parkNanos #java.lang.Thread.State: TIMED_WAITING (parking) LockSupport.parkUntil #java.lang.Thread.State: TIMED_WAITING (parking) 6、TERMINATED 线程克制。其次,利用 jstack 下令可以查察线程的堆栈信息,定位到简单的死锁,通过 jstack 也能定位CPU高的题目,详细步调如下:
- 查察当前占用cpu最高的历程pid(COMMAND列);
top
- 获取当前历程中全部线程占CPU的情况(也可top -p<pid>再按 H);
top -Hp <pid>printf "%x\n" <tid>;
- 查察占用最高的线程的堆栈状态。通过这个流程可以直接定位到哪个线程正在实验占用了大量的cpu。此中A10 就是过滤到关键词之后(A:after)10行信息。
jstack <pid> | grep -A10 <16进制tid>
死锁,Deadlock(重点关注)
实验中,Runnable
期待资源,Waiting on condition(重点关注)
期待获取监视器,Waiting on monitor entry(重点关注)
对象期待中,Object.wait() 或 TIMED_WAITING
停息,Suspended
壅闭,Blocked(重点关注)
克制,Parked
如果是由于同一个线程池里的两个线程夺取资源发生“死锁”,则需修改业务逻辑,改为差别的线程池来处理处罚大概利用单线程处理处罚。
如果是以execute方式提交线程,非常输出在控制台;如果是以submit方式提交线程,在控制台没有直接输出,必须调用Future.get()方法时,才可以捕获到非常;
一个线程出现了非常,不会影响线程池内里其他线程的正常实验,这个非常线程不会被接纳,而是会被线程池移撤消,同时创建一个新的线程放到线程池中。
参考文章
https://blog.csdn.net/hjfalz/article/details/117914630
https://www.shuzhiduo.com/A/MAzAPaApd9/
https://pdai.tech/md/java/thread/java-thread-x-juc-executor-ThreadPoolExecutor.html |