1,什么是双亲委派
AppClassLoader在加载类时,会向上委派,取查找缓存。
AppClassLoader ==>>ExtClassLoader ==>>BootStrapClassLoader
情况一
向上委派时查找到了,直接返回。
情况二
当委派到顶层之后,缓存中还是没有则到加载路径中查找,有则加载返回,没有就向下查找。
特点
向上委派:实际上就是查找缓存
向下查找:就是查找加载路径
好处
-
安全性,避免用户自己编写的类动态替换JAVA的以西核心类
-
同时也避免了类的重复加载。因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载了也是不同的两个类。
2,线程的生命周期,线程有哪些状态
线程的基本状态(5种)
创建(New),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)
-
新建(New):新创建了一个线程对象。
-
就绪(Runnable):线程创建后,其它线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的运行时间片。
-
运行(Running):就绪状态的线程获取了CPU的运行时间片,执行代码程序。
-
阻塞(Blacked):阻塞状态是线程因为某种原因就放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
-
死亡(Dead):线程执行玩了或者因异常退出了Run方法,该线程结束生命周期。
其中阻塞情况又大概分为以下几种:
-
等待阻塞(WAITING):运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中,进入这个状态后,线程无法自动唤醒,必须依靠其它线程调用notify或notifyALL才能被唤醒。是属于Object的方法。
-
同步阻塞(BLOCKED):运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
-
限期等待阻塞(TIMED_WAITING):线程调用了带有超时参数的
Thread.sleep(long millis)
、Object.wait(long timeout)
、Thread.join(long millis)
、LockSupport.parkNanos()
等方法后,会进入限期等待阻塞状态。 在指定的时间过期或者等待的条件满足后,线程会自动唤醒或者超时返回。 -
I/O 阻塞:线程因为等待输入输出完成而进入阻塞状态,例如调用了阻塞式的 I/O 操作(如读取文件、网络数据)。 当数据到达或者操作完成时,线程会从阻塞状态恢复,并继续执行后续操作。
3,线程状态
池的概念
锁池
所有需要竞争同步锁的线程都会放在锁池中,如果当前对象的锁已经被其中一个线程得到,则其它线程需要在这个锁池中等待,等待该对象的锁释放后再去竞争这个同步锁。当线程得到满足运行条件的同步锁后就会进入就绪队列等待CPU分配运行时间片。
等待池(监狱)
当我们调用了wait()方法后,该线程就会进入等待池中,等待池的线程不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争同步锁。值得注意的是notify()是从等待池中随机选出一名幸运线程放入锁池中。而notifyAll()则相当于,皇帝登基,大赦天下。把关在等待池中的线程全部释放到锁池中。
sleep
-
sleep是Thread类的静态本地方法,wait则是Object类的本地方法。
-
sleep方法不会释放lock,但wait会释放,而且加入等待队列中。 Sleep就是把cpu的执行资格和执行全释放出去,不在运行此线程,当定时时间结束后再取回CPU资源,参与cpu的调度,获取到cpu资源就可以继续运行了。且sleep持有锁,不会释放锁,是把锁带着进入了冻结状态。其它程序无法获取这个锁。(sleep就相当于现在的租借功能,把使用权定时让出入就是cpu的执行,但是所有权(锁)还是属于出租方,锁还是再该线程。等时间结束后,使用权又回归出租方了)
-
sleep方法不依赖于同步器synchronizad,但是wait需要依赖synchronizad关键字。
-
sleep不需要被唤醒(休眠之后退出阻塞状态)
-
sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。
-
sleep会让出CPU执行时间且强制上下文切换,则wait则不一定
yield
yield()执行后当前线程让出cpu执行权,进入就绪状态,但是议然保留了cpu的执行资格,所以哟可能cpu下次调度还会让这个线程执行。剪刀石头布你输了不认账,重来一把(调用yield()方法),但有可能还是我赢。
join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),线程B进入阻塞队列,直到线程A结束或中断线程。就像一场球赛上半场(B)打得起劲,裁判突然吹了哨子(A),这时B暂停,等裁判BB完了,吹起重新开赛的哨子(A运行死亡)球赛才能再开(B恢复运行。)
4,ThreadLocal的原理和使用场景
概念解释:
每一个Thread对象都含有一个ThreadLocalMap类型的成员变量threadLocals,它存储在本线程中所有ThreadLocal对象以及其对应的值
ThreadLocalMap由一个个Entry对象构成
Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和object构成。由此可见Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadMap对象。再以当前ThreadLocal对象为key,获取对应value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程范文容器的互斥性。
使用场景:
-
在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
-
线程间数据隔离
-
进行事务操作,用于存储线程事务信息,
-
数据库连接,Session会话管理
5,ThreadLocal内存泄漏的原因,如何避免
内存泄漏为程序在申请内存后,无法释放已经申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被消耗殆尽。
不再被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。
强引用:使用最普遍的引用(new),一个对象具有强引用,是不会被垃圾回收器回收的。当内存空间不足,java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会回收这个对象。
如果想取消引用和某个对象之间的关联,可以显示地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用的关联对象。在Java中,用Java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例。
ThreadLocalMap使用使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreadLocal)势必会被GC回收,这样就导致了ThreadLocalMap中key为null,而value还存在着强引用,只有线程退出以后,value的强引用链条才会断掉,但如果当前线程迟迟不结束的话,这些key为null的Entry的Value就会一直存在一条强引用链(红色链条)
key使用强引用
当ThreadLocalMap 的key为强引用回收ThreadLocal时,由于ThreadLocalMap还持有ThreadLocal的强引用,如果不手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap还持有ThreadLocal的弱引用,即使不手动删除,ThreadLocal也会被回收,当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法时候,会被清除value的值。
因此:**ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
ThreadLocal正确的使用方法
-
每次用完ThreadLocal都调用它的remove()方法清除数据。
-
将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进行清除掉。