去大厂面试,结果没想到一个Handler还有中高级几种问法,我慌了...

论坛 期权论坛     
选择匿名的用户   2021-5-30 03:23   248   0
<p><img alt="" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-ee8f215f478a225d1f08064db6a9624e.png"></p>
<h3>Handler深层次问题解答</h3>
<h3>ThreadLocal</h3>
<p>ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。</p>
<p>如果让你设计一个ThreadLocal,ThreadLocal 的目标是让不同的线程有不同的变量 V,那最直接的方法就是创建一个 Map,它的 Key 是线程,Value 是每个线程拥有的变量 V,ThreadLocal 内部持有这样的一个 Map 就可以了。你可能会设计成这样</p>
<p><img alt="" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-73d45d90e5ead27acc32f924e6eeb6c9.png"></p>
<p>实际上Java的实现是下面这样,Java 的实现里面也有一个 Map,叫做 ThreadLocalMap,不过持有 ThreadLocalMap 的不是 ThreadLocal,而是 Thread。Thread 这个类内部有一个私有属性 threadLocals,其类型就是 ThreadLocalMap,ThreadLocalMap 的 Key 是 ThreadLocal。</p>
<p><img alt="" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-b1717956fce199c25f9e7ae358e86615.png"></p>
<p>精简之后的代码如下</p>
<pre class="blockcode"><code>class Thread {
  //内部持有ThreadLocalMap
  ThreadLocal.ThreadLocalMap
    threadLocals;
}
class ThreadLocal&lt;T&gt;{
  public T get() {
    //首先获取线程持有的
    //ThreadLocalMap
    ThreadLocalMap map &#61;
      Thread.currentThread()
        .threadLocals;
    //在ThreadLocalMap中
    //查找变量
    Entry e &#61;
      map.getEntry(this);
    return e.value;  
  }
  static class ThreadLocalMap{
    //内部是数组而不是Map
    Entry[] table;
    //根据ThreadLocal查找Entry
    Entry getEntry(ThreadLocal key){
      //省略查找逻辑
    }
    //Entry定义
    static class Entry extends
    WeakReference&lt;ThreadLocal&gt;{
      Object value;
    }
  }
}
</code></pre>
<p>在Java的实现方案中,ThreadLocal仅仅只是一个代理工具类,内部并不持有任何线程相关的数据,所有和线程相关的数据都存储在Thread里面,这样的设计从数据的亲缘性上来讲,ThreadLocalMap属于Thread也更加合理。<strong>所以ThreadLocal的get方法,其实就是拿到每个线程独有的ThreadLocalMap</strong>。</p>
<p>还有一个原因,就是<strong>不容易产生内存泄漏</strong>,如果用我们的设计方案,ThreadLocal持有的Map会持有Thread对象的引用,这就意味着只要ThreadLocal对象存在,那么Map中的Thread对象就永远不会被回收。ThreadLocal的生命周期往往都比线程要长,所以这种设计方案很容易导致内存泄漏。</p>
<p>而Java的实现中Thread持有ThreadLocalMap,而且ThreadLocalMap里对ThreadLocal的引用还是弱引用,所以只要Thread对象可以被回收,那么ThreadLocalMap就能被回收。<strong>Java的实现方案虽然看上去复杂一些,但是更安全</strong>。</p>
<h3>ThreadLocal与内存泄漏</h3>
<p>但是一切并不总是那么完美,如果在线程池中使用ThreadLocal可能会导致内存泄漏,原因是线程池中线程的存活时间太长,往往和程序都是同生共死的,这就意味着Thread持有的ThreadLocalMap一直都不会被回收,再加上ThreadLocalMap中的Entry对ThreadLocal是弱引用,所以只要ThreadLocal结束了自己的生命周期是可以被回收掉的。<strong>但是Entry中的Value却是被Entry强引用的,所以即便Value的生命周期结束了,Value也是无法被回收的,从而导致内存泄漏</strong>。</p>
<p>所以我们可以通过try{}finally{}方案来手动释放资源</p>
<pre class="blockcode"><code>ExecutorService es;
ThreadLocal tl;
es.execute(()-&gt;{
  //ThreadLocal增加变量
  tl.set(obj);
  try {
    // 省略业务逻辑代码
  }finally {
    //手动清理ThreadLocal
    tl.remove();
  }
});
</code></pre>
<p>以上ThreadLocal内容主要参考自<a href="https://links.jianshu.com/go?to&#61;https%3A%2F%2Ftime.geekbang.org%2Fcolumn%2Farticle%2F93745">这里</a>。</p>
<h3>epoll机制</h3>
<p>epoll机制在Handler中的应用,在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,最终调用到epoll_wait()进行阻塞等待。此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。</p>
<p>这里有一篇深度好文<a href="https://www.jianshu.com/p/dfd940e7fca2">聊聊IO多路复用之select,pol
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP