从Android Handle看 线程与对象、ThreadLocal

最近面试,对线程有了更深的理解。

问题:

 

RuntimeException:Can't create handler inside thread that has not called Looper.prepare()。(不能在没有调用Looper.prepare()的线程内创建Handler)

Looper.prepare()是为了创建Looper、MessageQueue。那为什么在工作线程new Handler,需要Loop对象,为什么UI线程就不需要。对象和线程之间是什么关系,

 

易混淆点是: 多线程时,认为对象属于某一线程。(大错,特错

对象

 

  • 本质是 内存中的数据,且具有某种固定格式
  • 数据是存在于java虚拟机中,java虚拟机用 堆、方法区来存放数据信息
  • 数据分为:类信息的描述(类的名称、方法名、父类名称)、实例数据(字段的值,如Dog类的对象 中的名字 小黑)
  • 类信息的描述 存放在虚拟机的 方法区(只是叫这个名字,和java中的方法没啥关系) 中
  • 实例数据存放在 堆(heap)中,并保存指向 类信息的指针
  • 在虚拟机中 堆、方法区 是线程共享的,也就是线程都可以操作的 

 

各线程是共享内存地址空间,共享一个 堆的、所以共享堆 产生的对象。

 

打个比方:

会计用算盘(内存)算数,算盘上拨出的数字2(Integer对象,数据)。

当用两个手(线程)操作算盘(内存)拨出2+2。

那右手动的时候,不能说2(数据)属于右手(线程)。

 

线程:

 

程序的控制流 (手操作的一组流程,当然比喻不准确),表现为一组方法。而对象是方法操作的元素。

所以每个线程的 方法栈是独立的。这样A、B两个线程才能同时执行不同的方法。

 

Handler

handler可以从工作线程提交任务,然后UI线程执行。

因为Handler对象线程共享,而它们的操作可以分开,所以并不矛盾。

 

Looper:

其实框架在加载APP时已调用了Looper.prepareMainLooper();这个操作,在ActivityThread main方法(应用入口)里做的。

那既然Looper已经有了,且对象是线程共有的。为什么还要Looper.prepare()在工作线程new Looper。

这就涉及到了ThreadLocal。Looper有个静态字段

static final ThreadLocal<Looper>sThreadLocal

 

ThreadLocal:

(算盘右边5串珠子(数据),只允许右手(指定的线程)操作)

ThreadLocal像Map。key是线程本身、value是任意对象。通过get、set方法调用。

调用set时,就会把当前Thread作为Key值。调用get时也是如此。

而Looper.prepare()调用了:

sThreadLocal.set(new Looper(quitAllowed));

  因框架启动在UI线程,所以ThreadLocal Key值是UI线程,Value为对应的Looper

 

Handler有属性Looper,new Handler---》Looper.myLooper()---->sThreadLocal.get();

因为工作线程这个Key值没有对应的Value所以就回报错。

 

解决:

所以从上面可以看出,只要Handler在new时调用 ThreadLocal能get到Looper就行。所以向Handler传Looper参数就行。

 

new Thread(){
            @Override
            public void run() {
                super.run();
                new Handler(Looper.getMainLooper());
            }
        }.start();

 

 

 

 

 

总结:

对象不存在属于某个进程这种描述,对象是数据,进程是操作流程。之所以出现工作线程需要ThreadLocal,将对象和线程产生了关联。

 

 

发布了182 篇原创文章 · 获赞 31 · 访问量 25万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览