当前位置: 首页 > news >正文

手机培训网站建设wordpress后台界面样式

手机培训网站建设,wordpress后台界面样式,wordpress tag内链接,如何创建网站挣钱【尚硅谷】学习视频#xff1a;https://www.bilibili.com/video/BV1ar4y1x727【黑马程序员】学习视频#xff1a;https://www.bilibili.com/video/BV15b4y117RJ 参考书籍 《实战 JAVA 高并发程序设计》 葛一鸣 著《深入理解 JAVA 虚拟机 | JVM 高级特性与最佳实践》 周志明 著… 【尚硅谷】学习视频https://www.bilibili.com/video/BV1ar4y1x727【黑马程序员】学习视频https://www.bilibili.com/video/BV15b4y117RJ 参考书籍 《实战 JAVA 高并发程序设计》 葛一鸣 著《深入理解 JAVA 虚拟机 | JVM 高级特性与最佳实践》 周志明 著 系列目录 学习笔记Java 并发编程①_基础知识入门学习笔记Java 并发编程②_共享模型之管程学习笔记Java 并发编程③_共享模型之内存学习笔记Java 并发编程④_共享模型之无锁学习笔记Java 并发编程⑤_共享模型之不可变学习笔记Java 并发编程⑥_共享模型之并发工具_线程池学习笔记Java 并发编程⑥_共享模型之并发工具_JUC学习笔记Java并发编程补CompletableFuture学习笔记Java并发编程补ThreadLocal 前言 系列文章目录中的前七篇文章的相关学习视频都是黑马程序员深入学习 Java 并发编程JUC 并发编程全套教程。然而这个时长为 32h 的视频中并没有 ThreadLocal 的相关知识这个知识点是非常重要的需要掌握。故找了相关的视频来学习这个重要的知识点写在了系列文章目录中的第九篇博客里。第九篇的相关学习视频是 尚硅谷 JUC 并发编程对标阿里 P6-P7 若文章内容或图片失效请留言反馈。部分素材来自网络若不小心影响到您的利益请联系博主删除。写这篇博客旨在制作笔记方便个人在线阅览巩固知识。无他用。 ThreaLocalT该类提供线程局部变量 常问问题 请描述 ThreadLocal 中 ThreadLocalMap 的数据结构和关系ThreadLocal 中的 key 是弱引用这是为什么ThreadLocal 中的内存泄露问题的原因在 ThreadLocal 中最后为什么要加 remove() 方法 1.基本概念 ThreadLocal 提供局部变量。这些变量和正常的变量是不同的每一个线程在访问 ThreadLocal 实例的时候通过 ThreadLocal 提供 的 get() 或 set() 方法都有着属于自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段使用它的目的是把状态例用户ID 或 事务ID与线程关联起来。 类似于填表100 个人和一支笔每个人都得挨个填写必须保证不出现哄抢现象负责谁也填不完如果是 100 个人人手一支笔那么大家就可以很快的完成填表操作。 ThreadLocal 可以让每一个线程都有自己专属的本地变量副本人人有份。每一个线程都绑定了自己的值通过 get() 方法、set() 方法获取到默认值或是将其值更改为当前线程中所存的副本的值从而避免了线程安全的问题。 不过为每一个线程分配一个对象的工作并不是由 ThreadLocal 完成的而是需要在应用层面保证的。如果在应用中为每一个线程分配相同的对象实例此时 ThreadLocal 也不能保证线程安全。 2.相关 API 变量和类型方法描述Tget()返回当前线程的线程局部变量副本中的值protected TinitialValue()返回此线程局部变量的当前线程的“初始值”voidremove()删除此线程局部变量的当前线程值voidset(T value)将此线程局部变量的当前线程副本设置为指定值static S ThreadLocalSwithInitial(Supplier? extends S supplier创建一个线程局部变量3.实操 3.1.注意事项 3.1.1.当前线程的 “初始值” 这里提一下 withInitial(Supplier? extends S supplier 和 initialValue() 上面的这俩方法的功能是差不太多的 initialValue() 可以通过匿名内部类的方式 返回此线程局部变量的当前线程的 “初始值” ThreadLocalInteger saleVolume new ThreadLocalInteger() {Overrideprotected Integer initialValue() {return 0;} };但是匿名内部类这种写法的还是过于繁琐了更推荐 withInitial(Supplier? extends S supplier 的方式来创建一个线程局部变量 ThreadLocalInteger saleVolume ThreadLocal.withInitial(() - 0);3.1.2.回收自定义的 ThreadLocal 变量 这里提一下 ThreadLocal 的相关要求阿里巴巴 Java开发手册 一般来说我们 必须要回收自定义的 ThreadLocal 变量。尤其是在线程池的场景下线程经常会被复用如果不清理掉自定义的 ThreadLocal 变量很可能会影响后续业务逻辑和造成内存泄露等问题。尽量在 try-finally 块进行回收 ​objectThreadLocal.set(userInfo);try { // ... } finally { objectThreadLocal.remove(); }3.2.案例一 案例5 个销售卖房子公司按照总销售额来统计最终五个人平均分配奖金那么此时我们只需要知道最终的销售额就行 然而时代变了。现在是希望各位销售凭本事抽提成了按照各自的出单数来发放对应的奖金了。 House.java public class House {int saleCount 0;public synchronized void saleHouse() {saleCount;}ThreadLocalInteger saleVolume ThreadLocal.withInitial(() - 0);public void saleVolumeByThreadLocal() {saleVolume.set(1 saleVolume.get());} }ThreadLocalDemo_1.java Slf4j(topic c.Demo_1) public class ThreadLocalDemo_1 {public static void main(String[] args) throws InterruptedException {House house new House();for (int i 1; i 5; i) {new Thread(() - {int size new Random().nextInt(5) 1;try {for (int j 1; j size; j) {house.saleHouse();house.saleVolumeByThreadLocal();}log.info(【{} 号销售卖出了 {} 套】, Thread.currentThread().getName(), house.saleVolume.get());} finally {house.saleVolume.remove();}}, String.valueOf(i)).start();}TimeUnit.MILLISECONDS.sleep(300);System.out.println(----------------------------------------------------------------);log.info(【一共卖出了 {} 套】, house.saleCount);} }控制台输出信息 19:13:46.044 [1] INFO c.Demo_1 - 【1 号销售卖出了 2 套】 19:13:46.044 [4] INFO c.Demo_1 - 【4 号销售卖出了 4 套】 19:13:46.044 [5] INFO c.Demo_1 - 【5 号销售卖出了 1 套】 19:13:46.044 [2] INFO c.Demo_1 - 【2 号销售卖出了 2 套】 19:13:46.044 [3] INFO c.Demo_1 - 【3 号销售卖出了 5 套】 ---------------------------------------------------------------- 19:13:46.345 [main] INFO c.Demo_1 - 【一共卖出了 14 套】3.3.案例二 此处仍然是在证明回收自定义的 ThreadLocal 变量的重要性。 MyData.java public class MyData {ThreadLocalInteger threadLocalField ThreadLocal.withInitial(() - 0);public void add() {threadLocalField.set(1 threadLocalField.get());} }ThreadLocalDemo_2 Slf4j(topic c.ThreadLocalDemo_2) public class ThreadLocalDemo_2 {public static void main(String[] args) {MyData myData new MyData();ExecutorService threadPool Executors.newFixedThreadPool(3);try {for (int i 0; i 10; i) {try {threadPool.submit(() - {Integer beforeInt myData.threadLocalField.get();myData.add();Integer afterInt myData.threadLocalField.get();log.info(【beforeInt{}、afterint{}】, beforeInt, afterInt);});} finally {myData.threadLocalField.remove();}}} catch (Exception e) {e.printStackTrace();} finally {threadPool.shutdown();}} }控制台输出信息 21:23:43.817 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt0、afterint1】 21:23:43.817 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt0、afterint1】 21:23:43.817 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt0、afterint1】 21:23:43.828 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt1、afterint2】 21:23:43.828 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt1、afterint2】 21:23:43.828 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt1、afterint2】 21:23:43.829 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt2、afterint3】 21:23:43.829 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt2、afterint3】 21:23:43.829 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt2、afterint3】 21:23:43.829 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt3、afterint4】 21:23:43.829 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt3、afterint4】如果没有调用 ThreadLocal 的 remove() 的话则会造成如下的结果 21:24:53.053 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt0、afterint1】 21:24:53.053 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt0、afterint1】 21:24:53.053 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt0、afterint1】 21:24:53.064 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt1、afterint2】 21:24:53.064 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt1、afterint2】 21:24:53.064 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt1、afterint2】 21:24:53.064 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt2、afterint3】 21:24:53.064 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt2、afterint3】 21:24:53.064 [pool-1-thread-3] INFO c.Demo_2 - 【beforeInt3、afterint4】 21:24:53.064 [pool-1-thread-2] INFO c.Demo_2 - 【beforeInt3、afterint4】 21:24:53.064 [pool-1-thread-1] INFO c.Demo_2 - 【beforeInt2、afterint3】如上所示后一个任务被前一个任务影响到了这不符合线程池中提交的任务分别运行在独立的线程中的想法。 3.4.小结 因为每个 Thread 内有自己的实例副本且该副本只能由当前线程自己使用既然其他 Thread 不可访问那就不存在多线程共享的问题统一设置初始值但是每个线程对这个值的修改都是互相独立的也就是各改各的 如何才能不争抢 加入 synchronized 或者 lock 控制线程的访问顺序ThreadLocal 人手一份大家各自安好无需抢夺 3.5.SimpleDateFormat 既然上面提到了 阿里巴巴 Java开发手册 的并发处理那就再提一个 SimpleDateFormat 的介绍 SimpleDateFormat 是线程不安全的类一般不要定义为 static 变量如果定义为 static则必须加锁或者使用 DateUtils 工具类。 正例注意线程安全使用 DateUtils。亦推荐如下处理 private static final ThreadLocalDateFormat df new ThreadLocalDateFormat() { Override protected DateFormat initialValue() { return new SimpleDateFormat(yyyy-MM-dd); } }; 说明如果是 JDK 8 的应用可以使用 Instant 代替 DateLocalDateTime 代替 CalendarDateTimeFormatter 代替 SimpleDateFormat 官方给出的解释simple beautiful strong immutable thread-safe。 4.ThreadLocal 源码 4.1.ThreadLocalMap Thread 和 ThreadLocal java/lang/Thread.java ThreadLocal.ThreadLocalMap inheritableThreadLocals null;ThreadLocal 和 ThreadLocalMap java/lang/ThreadLocal.java static class ThreadLocalMap {... ... }ThreadLocalMap 是 ThreadLocal 中的一个静态内部类 ThreadLocalMap 实际上就是一个以 threadLocal 实例为 key任意对象为 value 的 Entry 对象 void createMap(Thread t, T firstValue) {t.threadLocals new ThreadLocalMap(this, firstValue); }当我们为 threadLocal 变量赋值时实际上就是把当前 threadLocal 实例为 key值为 value 的 Entry 对象存在 threadLocalMap 中 4.2.get() 方法 get() 方法先取得当前线程的 ThreadLocalMap 对象然后将自己作为 key 来取得内部的实际数据 ThreadLocal.get() 源码java/lang/ThreadLocal.java 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(); }相关方法 java/lang/Thread.java ThreadLocal.ThreadLocalMap threadLocals null;java/lang/ThreadLocal.java ThreadLocalMap getMap(Thread t) {return t.threadLocals; }private ThreadLocalMap(ThreadLocalMap parentMap) {... ...private Entry getEntry(ThreadLocal? key) {int i key.threadLocalHashCode (table.length - 1);Entry e table[i];if (e ! null e.get() key)return e;elsereturn getEntryAfterMiss(key, i, e);}... ... }private T setInitialValue() {T value initialValue();Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);elsecreateMap(t, value);return value; }如果不给 ThreadLocal 设值的话一般会默认设 null 值protected T initialValue() 为了避免日后程序运行时报空指针异常一定要给 ThreadLocal 设置初始值。 protected T initialValue() {return null; }void createMap(Thread t, T firstValue) {t.threadLocals new ThreadLocalMap(this, firstValue); }private static final int INITIAL_CAPACITY 16;ThreadLocalMap(ThreadLocal? firstKey, Object firstValue) {table new Entry[INITIAL_CAPACITY];int i firstKey.threadLocalHashCode (INITIAL_CAPACITY - 1);table[i] new Entry(firstKey, firstValue);size 1;setThreshold(INITIAL_CAPACITY); }4.3.set() 方法 在 set() 方法中首先获得当前线程对象然后通过 getMap() 方法获取到线程的 ThreadLocalMap并将值存入到 ThreadLocalMap 中 java/lang/ThreadLocal.java public void set(T value) {Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);elsecreateMap(t, value); }private void set(ThreadLocal? key, Object value) {Entry[] tab table;int len tab.length;int i key.threadLocalHashCode (len-1);for (Entry e tab[i];e ! null;e tab[i nextIndex(i, len)]) {ThreadLocal? k e.get();if (k key) {e.value value;return;}if (k null) {replaceStaleEntry(key, value, i);return;}}tab[i] new Entry(key, value);int sz size;if (!cleanSomeSlots(i, sz) sz threshold)rehash(); }4.4.remove() 方法 java/lang/ThreadLocal.java public void remove() {ThreadLocalMap m getMap(Thread.currentThread());if (m ! null)m.remove(this); }// Remove the entry for key. private void remove(ThreadLocal? key) {Entry[] tab table;int len tab.length;int i key.threadLocalHashCode (len-1);for (Entry e tab[i];e ! null;e tab[i nextIndex(i, len)]) {if (e.get() key) {e.clear();expungeStaleEntry(i);return;}} }4.4.小结 分析 可以这样理解ThreadLocalMap 从字面上就可以看出这是一个保存了 Threadlocal 对象的 map准确的说ThreadLocalMap 是以 ThreadLocal 为 Key 的 Entry一个经历了两层包装的 ThreadLocal 对象。 可以这样理解JVM 内部维护了一个线程版的 MapThreadLocal, Value。其通过 ThreadLocal 对象的 set() 方法以 ThreadLocal 对象自己为 key存入 ThreadLocalMap 中在每一个线程要用到这个 T 的时候都需要当前的线程去 Map 中获取通过这样的方式使得每一个线程都有自己独属的变量人手一份不争不抢。既然竞争条件都被消除了那么它在并发模式下就是绝对安全的变量。 总结 ThreadLocal 是一个壳子真正的存储结构是 ThreadLocal 中的静态内部类 ThreadLocalMap。 每个 Thread 对象维护着一个 ThreadLocalMap 的引用ThreadLocalMap 是 ThreadLocal 的内部类用 Entry 来进行存储。 调用 ThreadLocal 的 set() 方法时实际上就是往 ThreadLocalMap 设置值key 是 ThreadLocal 对象值 Value 是传递进来的对象调用 ThreadLocal 的 get() 方法时实际上就是往 ThreadLocalMap 获取值key 是 ThreadLocal 对象 ThreadLocal 本身并不存储值ThreadLocal 是一个壳子它只是自己作为一个 key 来让线程从 ThreadLocalMap 获取 value。 正因为这个原理所以 ThreadLocal 能够实现 “数据隔离”获取当前线程的局部变量值不受其他线程影响。 5.内存泄露问题 不会再次被使用的对象 或 变量占用的内存 不能被回收就是内存泄露。 5.1.四种引用 有关四种引用的更多信息我都写在了这篇博客里【Java 虚拟机②】垃圾回收此处只是提个大概。 JAVA 技术允许使用 finalize() 方法 在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。 java/lang/Object.java protected void finalize() throws Throwable { }参考书籍《深入理解 JAVA 虚拟机 | JVM 高级特性与最佳实践》 周志明 著 在 JDK 1.2 版之后Java 对引用的概念进行了扩充将引用分为四种类型。 强引用Strongly Re-ference、软引用Soft Reference、弱引用Weak Reference、虚引用Phantom Reference。 这 4 种引用的强度依次逐渐减弱。 强引用是最传统的 “引用” 的定义是指在程序代码之中普遍存在的引用赋值 类似于 Object objnew Object() 这种引用关系。无论处于何种情况下只要强引用关系还存在垃圾收集器就永远不会回收掉被引用的对象。因此强引用是造成 JAVA 内存泄露的主要原因之一。 软引用是用来描述一些还有用但非必须的对象。 只被软引用关联着的对象在系统将要发生内存溢出异常前会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存才会抛出内存溢出异常。在 JDK 1.2 版之后提供了 SoftReference 类来实现软引用。 弱引用也是用来描述那些非必须对象。 但是它的强度比软引用更弱一些被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作无论当前内存是否足够都会回收掉只被弱引用关联的对象。在 JDK 1.2 版之后提供了 WeakReference 类来实现弱引用。 虚引用也称为 “幽灵引用” 或者 “幻影引用”它是最弱的一种引用关系。 一个对象是否有虚引用的存在完全不会对其生存时间构成影响也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在 JDK 1.2 版之后提供了 PhantomReference 类来实现虚引用。 强引用 只有所有 GC Roots 对象都不通过 强引用 引用该对象该对象才能被垃圾回收。软引用SoftReference 仅有软引用引用该对象时在垃圾回收后内存仍不足的情况下会再次触发垃圾回收回收软引用对象。 可以配合 引用队列 来释放软引用自身。 软引用通常用在对内存敏感的程序中比如高速缓存中就有用到软引用。 简言之对于只有软引用的对象而言内存足够的时候就保留它内存不够用就会回收它。弱引用WeakReference 仅有弱引用引用该对象时在垃圾回收时无论内存是否充足都会回收弱引用对象。 但若要释放弱引用自身则需要配合 引用队列 来释放弱引用自身虚引用PhantomReference必须配合 引用队列 使用主要配合 ByteBuffer。 被引用对象回收时会将虚引用入队由 Reference Handler 线程调用虚引用相关方法释放 直接内存 例如由 Reference Handler 线程通过 Cleaner 的 clean 方法调用 Unsafe.freeMemory 来释放直接内存 注意细节 虚引用必须要和引用队列ReferenceQueue联合使用 虚引用需要 java.lang.PhantomReference 类来实现。与其他几个引用不同虚引用不会决定对象的生命周期。 如果一个对象只是持有虚引用那么它在任何时候都可能被垃圾回收期回收。它不可以被单独使用也不可以通过它来访问对象。PhantomReference 的 get() 方法总是返回 null因此无法访问对应的引用对象。 虚引用的主要作用是跟踪对象被垃圾回收的状态。 它仅仅是提供了一种确保对象被 finalize 之后做某些事情的通知机制。处理监控通知使用 设置虚引用的唯一目的就是为了在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理用来实现比 finalize 机制更灵活的回收操作。 小结 5.2.Thread.exit() 参考书籍《实战 JAVA 高并发程序设计》 葛一鸣 著 在了解了 ThreadLocal 的内部实现后我们自然会引出一个问题。 那就是这些变量是维护在 Thread 类内部的ThreadLocalMap 定义所在类这也意味着只要线程不退出对象的引用将一直存在。 当线程退出时Thread 类会进行一些清理工作其中就包括清理 ThreadLocalMap java/lang/Thread.java 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 中它实际保存在线程持有的 threadLocalMap 内可能会使系统出现内存泄露这里我的意思是你设置了对象到 ThreadLocal 中但是不清理它在你使用几次后这个对象也不再有用了但是它却无法被回收。 此时如果你希望及时回收对象最好使用 ThreadLocal.remove() 方法将这个变量移除。就像我们习惯性地关闭数据库连接一样。如果你确实不需要这个对象了那么就应该告诉虚拟机请把它回收掉防止内存泄露。 另外一种有趣的情况是 JDK 也可能允许你像释放普通变量一样释放 ThreadLocal。 例我们有时候为了加速垃圾回收会特意写出类似 obj null 之类的代码。如果这么做obj 所指向的对象就会更容易地被垃圾回收器发现从而加速回收。同理如果对于 ThreadLocal 的变量我们也手动将其设置为 null比如 tl null。那么这个 ThreadLocal 对应的所有线程的局部变量都有可能被回收。 5.3.源代码使用弱引用 public class T {volatile boolean flag;public static void main(String[] args) {ThreadLocalString t1 new ThreadLocal();t1.set(zzyybs126.com);t1.get();} }为什么源代码要用弱引用 当 function01 方法执行完毕后栈帧销毁强引用 t1 也就没有了。 但问题在于此时的 ThreadLocalMap 里的某个 entry 的 key 依旧指向这这个对象。 如果这个 key 引用是强引用就会导致 key 指向的 ThreadLocal 对象及 v 指向的对象不能被 GC 回收造成内存泄漏如果这个 key 引用是弱引用就大概率会减少内存泄漏的问题还有一个 key 为 null 的坑)。 使用弱引用就可以使 ThreadLocal 对象在方法执行完毕后顺利被回收且 Entry 的 key 引用指向为 null 此后我们调用 get()、set()、remove() 方法的时候就会尝试去删除 key 为 null 的 entry可以释放 value 对象所占用的内存。 这里我再贴一下 《实战 JAVA 高并发程序设计》 书中的内容。 参考书籍《实战 JAVA 高并发程序设计》 葛一鸣 著 ThreadLocalMap 的实现使用了弱引用。弱引用是比强引用弱得多的引用。Java 虚拟机在垃圾回收时如果发现弱引用就会立即回收。ThreadLocalMap 内部由一系列 Entry 构成每一个 Entry 都是 WeakReferenceThreadLocal java/lang/ThreadLocal.java static class ThreadLocalMap {static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}... ...}这里的参数 k 就是 Map 的 keyv 就是 Map 的 value。 其中 k 也就是 ThreadLocal 实例作为弱引用使用super(k) 就是调用了 WeakReference 的构造函数。 因此虽然这里使用 ThreadLocal 作为 Map 的 key但是实际上,它并不真的持有 ThreadLocal 的引用。 而当 ThreadLocal 的外部强引用被回收时ThreadLocalMap 中的 key 就会变成 null。 当系统进行 ThreadLocalMap清理时比如将新的变量加入表中就会自动执行一次清理。不过 JDK 不一定会进行一次彻底清理时就会自然将这些垃圾数据回收。 5.4.key 为 null 的 entry 我们为 threadLocal 变量赋值实际上就是当前的 EntrythreadLocal 实例为 key值为 null往这个 threadLocalMap 中存放。 Entry 中的 key 是弱引用在 threadLocal 外部强引用被置为 nullt1 null后如果系统发生 GC 时根据可达性分析这个 threadLocal 就没有办法如何一条链路能够引用到它这个 ThreadLocal 势必会被回收。这样一来ThreadLocalMap 中就会出现 key 是 null 的 Entry就没有办法访问到这些 key 是 null 的 Entry 的 value。如果当前线程迟迟不结束的话比如正好在使用线程池这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链Thread Ref - Thread - ThreadLocalMap - Entry - value这样就无法回收造成了内存泄露。 当然如果当前 thread 运行结束threadLocal、threadLocalMap、Entry 没有引用链是可达的在 GC 时就可以被系统回收。 然而在实际开发中我们通常会使用线程池来维护我们的线程比如 Executors.newFixedThreadPool() 创建线程的时候为了复用线程我们是不会让它结束的此时 threadLocal 的内存就值的我们小心了。 虽然弱引用保证了 key 指向的 ThreadLocal 对象可以被及时回收但是 v 指向的 value 对象是需要 ThreadLocalMap 调用 get()、set() 时发现 key 为 null才会去回收整个 entry、value因此弱引用不能百发百保证内存不会泄露。 我们需要在不使用某个 ThreadLocal 对象后手动调用 remove() 方法来删除它尤其是在线程池中。这里不仅有内存泄露的问题还有旧线程遗留的数据对当前线程造成影响的问题因为线程池中的线程是复用的这就意味着这个线程的 ThreadLocalMap 的对象也是重复使用的如果我们不手动调用 remove() 方法那么后面的线程就有可能获取到上个线程遗留下来的 value 值从而造成 Bug。 5.5.清除脏 Entry 在 java/lang/ThreadLocal.java 中的静态内部类 ThreadLocalMap 有这样一个方法 expungeStaleEntry() 这个方法会找到脏 Entry即 key null 的 Entry然后进行删除。 private int expungeStaleEntry(int staleSlot) {... ...Entry e;int i;for (i nextIndex(staleSlot, len); (e tab[i]) ! null; i nextIndex(i, len)){ThreadLocal? k e.get();if (k null) {e.value null;tab[i] null;size--;} else { ... }}return i; }在 ThreadLocal 的生命周期里针对 ThreadLocal 的内存泄露问题ThreadLocal 中的 get()、set()、remove() 方法都会间接地调用到这个 expungeStaleEntry() 方法来清理掉 key 是 null 的脏 entry 这也就是 阿里巴巴 Java开发手册 强制要求使用 ThreadLocal.remove() 的原因所在。 5.6.小结 一定要设置初始值ThreadLocal.withInitial(() - 初始值)建议用 static 修饰用完之后一定要手动 remove() 操作 ThreadLocal 阿里巴巴 Java开发手册 ThreadLocal 可以实现线程的数据隔离但这不在它本身而在于 Thread 的 ThreadLocalMap。 故 ThreadLocalMap 可以只初始化一次分配一次内存空间足以没必要作为成员变量多次被初始化。 总结尚硅谷版 学习视频【尚硅谷】ThreadLocal 之小结 ThreadLocal 并不解决线程间共享数据的问题ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景ThreadLocal 通过隐式的在不同线程内创建独立实例副本从而避免了实例线程安全的问题。每个线程持有一个只属于自己的专属 Map 并维护了 ThreadLocal 对象与具体实例的映射该 Map 由于只被持有它的线程访问故不存在线程安全以及锁的问题ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用避免了 ThreadLocal 对象无法被回收的问题都会通过 expungeStaleEntry()cleanSomeSlots()replaceStaleEntry() 这三个方法回收键为 null 的 Entry 对象的值即为具体实例以及 Entry 对象本身从而防止内存泄漏属于安全加固的方法清除脏 entry防止内存泄露群雄逐鹿起纷争人各一份天下安 总结黑马程序员版 学习视频【黑马程序员】Java 面试宝典_ThreadLocal 要求 掌握 ThreadLocal 的作用与原理掌握 ThreadLocal 的内存释放时机 作用 ThreadLocal 可以实现 资源对象 的线程隔离让每个线程各用各的 资源对象避免争用引发的 线程安全问题ThreadLocal 同时实现了线程内的资源共享 原理每个线程内有一个 ThreadLocalMap 类型的成员变量静态内部类用来存储资源对象 调用 set 方法就是以 ThreadLocal 自己作为 key资源对象作为 value放入当前线程的 ThreadLocalMap 集合中调用 get 方法就是以 ThreadLocal 自己作为 key到当前线程中查找关联的资源值调用 remove 方法就是以 ThreadLocal 自己作为 key移除当前线程关联的资源值 ThreadLocalMap 的一些特点 key 的 hash 值 统一分配初始容量 16扩容因子 2/3扩容容量翻倍key 索引冲突后用开放寻址法解决冲突 弱引用 keyThreadLocalMap 中的 key 被设计为弱引用原因如下 Thread 可能需要长时间运行如线程池中的线程如果 key 不再使用需要在内存不足GC时释放其占用的内存 内存释放时机ThreadLocalMap 中的 key 为弱引用value 是强引用 被动 GC 释放 key 仅是让 key 的内存释放关联 value 的内存并不会释放 懒惰被动释放 value get key 时发现是 null key则释放其 value 内存set key 时会使用启发式扫描清除临近的 null key 的 value 内存启发次数与元素个数是否发现 null key 有关 主动 remove 释放 key、value 会同时释放 key、value 的内存也会清除临近的 null key 的 value 内存推荐使用它因为一般使用 ThreadLocal 时都把它作为静态变量即强引用因此无法被动依靠 GC 回收
http://www.dnsts.com.cn/news/25721.html

相关文章:

  • 网站建设活动方案wordpress图片本地化
  • 莒县做网站番禺网站
  • 营销型网站有哪些app个人博客网站怎么做
  • 商城网站的建设定位wordpress数组遍历
  • 建设一个电商网站的流程wordpress产品属性搭配
  • 西部数码 空间做2个网站域名过户流程
  • 河北建设厅官方网站八大员考试科技成就
  • 盐城最专业网站建设网站排名优化企业营销型网站建设规划
  • 查询网站whois如果网站被攻击了
  • 做电商网站搭建就业岗位做网站一年赚多少钱
  • 百度怎么搜索到自己的网站全景网站怎么做
  • 文化体育局网站建设计算机网站建设与开发
  • 百度搜索指数排行北京seo关键词排名优化
  • 深圳做网站便宜郴州网站建设案例
  • 昆明做网站推网站外包 多少钱
  • 网站开发系统毕业综合实践报告wordpress更新域名
  • 沈阳做网站哪家最便宜广告投放平台投放
  • 德州网站推广怎么做网站优化 sit
  • 厦门市海沧区建设局网站WordPress开通用户投稿功能
  • 关于网站制作的论文宽带营销案例100例
  • 网站开发费用网站编程学习
  • 网站有了订单邮箱提醒代码手机制作图片
  • 网站推广策划的策略wordpress好用
  • 事件网站推广永嘉网站建设工作室
  • 做实体店优惠券的网站dw制作网页的代码
  • 汕头专业网站建设公司前端网站主题怎么做
  • 网站建设的公司哪家好福州网站运营
  • 建手机版网站品牌建设
  • 传媒网站建设方案网站维护 英语
  • 自己做服务器的网站吗网站建设资源kindle