写点什么

Java 基础面试题【六】线程 (2)

作者:派大星
  • 2023-10-10
    辽宁
  • 本文字数:2372 字

    阅读完需:约 8 分钟

Thread、Runable 的区别

Thread 和 Runnable 的实质是继承关系,没有可比性。无论使用 Runnable 还是 Thread,都会 new Thread,然后执行 run 方法。用法上,如果有复杂的线程操作需求,那就选择继承 Thread,如果只是简单的执行一个任务,那就实现 runnable。

对守护线程的理解

  • 守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个 JVM 中所有非守护线程的保姆;

  • 守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;


注意:由于守护线程的终止是自身无法控制的,因此千万不要把 IO、File 等重要操作逻辑分配给它;因为它不靠谱;

守护线程的作用是什么?

举例,GC 垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的 Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM 上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

应用场景:

  • 来为其它线程提供服务支持的情况;

  • 或者在任何情况下,程序结束时,这个线


程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。thread.setDaemon(true)必须在thread.start() 之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。


在 Daemon 线程中产生的新线程也是 Daemon 的。守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。


Java 自带的多线程框架,比如 ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用 Java 的线程池。

ThreadLocal 的原理和使用场景

ThreadLocal 的原理

  • 每一个 Thread 对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有 ThreadLocal 对象及其对应的值

  • ThreadLocalMap 由一个个Entry对象构成,Entry 继承自WeakReference<ThreadLocal<?>>,一个 Entry 由ThreadLocal对象Object 构成。由此可见,Entry 的 key 是 ThreadLocal 对象并且是一个弱引用。当没指向 key 的强引用后,该 key 就会被垃圾收集器回收,

  • 当执行 set 方法时,ThreadLocal 首先会获取当前线程对象,然后获取当前线程的 ThreadLocalMap 对象。再以当前 ThreadLocal 对象为 key,将值存储进 ThreadLocalMap 对象中。

  • get 方法执行过程类似。ThreadLocal 首先会获取当前线程对象,然后获取当前线程的 ThreadLocalMap 对象。再以当前 ThreadLocal 对象为 key,获取对应的 value。


由于每一条线程均含有各自私有的 ThreadLocalMap 容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:

  1. 在进行对象跨层传递的时候,使用 ThreadLocal 可以避免多次传递,打破层次间的约束。

  2. 线程间数据隔离

  3. 进行事务操作,用于存储线程事务信息。

  4. 数据库连接,Session 会话管理。


Spring 框架在事务开始时会给当前线程绑定一个 JdbcConnection,在整个事务过程都是使用该线程绑定的 connection 来执行数据库操作,实现了事务的隔离性。Spring 框架里面就是用的 ThreadLocal 来实现这种隔离

ThreadLocal 内存泄露原因,如何避免

  • 内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露

  • 强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java 虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为 null,这样可以使 JVM 在合适的时间就会回收该对象。

  • 弱引用:JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在 Java 中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。


ThreadLocal 的实现原理,每一个 Thread 维护一个 ThreadLocalMap,key 为使用弱引用的 ThreadLocal 实例,value 为线程变量的副本



ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 不存在外部强引用时,Key(ThreadLocal)势必会被 GC 回收,这样就会导致 ThreadLocalMap 中 key 为 null,而 value 还存在着强引用,只有 thead 线程退出以后,value 的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链(红色链条)


  • key 使用强引用当 hreadLocalMap 的 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 值,进而清除掉。法


如有问题,欢迎加微信交流:w714771310,备注- 技术交流  。或关注微信公众号【码上遇见你】。




发布于: 刚刚阅读数: 5
用户头像

派大星

关注

微信搜索【码上遇见你】,获取更多精彩内容 2021-12-13 加入

微信搜索【码上遇见你】,获取更多精彩内容

评论

发布
暂无评论
Java基础面试题【六】线程(2)_Java 面试题_派大星_InfoQ写作社区