ThreadLocal作用
在当前线程中存放属于该线程的数据
ThreadLocal存储算法记录
将当前线程的ThreadLocal作为key,将存放的值作为value,使用当前线程内部Value的一个对象数组table存放,key的index为ThreadLocal的引用的hash和当前线程内部Value对象的mask(mask:用于将hash转化为指数(indices))相与的结果,存放的值的index为key的index+1
为什么不用一个ThreadManager去管理比如维护一个ConcrrentHashMap?主要有两个问题
1、防止线程竞争所带来的效率问题 2、防止线程引用被强引用而在线程完成工作后不能立即被回收
其实主要是第一个问题,第二个问题可以用WeakReference解决,但第一个问题会随着时间越来越大
ThreadLocal+Value解决方案
ThreadLocal的内部存储结构是给每一个Thread创建一个Value,用来保存自己的数据,这个Value内部维护一个数组用来存放当前线程的数据
线程的Value如果为空则通过任意ThreadLocal创建,因为是每一个线程都有自己的ThreadLocal所以不用担心线程同步的问题,Threadlocal的set、remove、get方法都是间接调用到了Value内部的方法,由于ThreadLocal仅存放Value的临时变量所以不会泄露,Value利用当前的ThreadLocal作为key计算出其在table中的index,key就存放在这个Index上,而其值则存放在index+1的位置,这样就使得每一个线程都具有其自己的数据,而且避免了多线程的竞争和内存泄露的问题
Handler与Looper
在子线程中new一个Handler时,如果不在构造参数中传入一个Looper,则必须在new之前调用Looper#prepare()方法为该子线程创建一个Looper,该方法内部使用ThreadLocal存储Looper,这样可以让每个线程拥有自己的Looper,Handler在构造器初始化时会调用Looper#myLooper()方法,该方法会从ThreadLocal中获取一个Looper对象,你也可以在构造器中传入一个looper对象,如果是通过Looper#getMainLooper()得到的looper对象,不需要再调用looper#loop(),因为系统已经调用过了,系统的looper已经开始不断遍历MessageQueue了,只要有新的Message到来,looper就会调用该Message持有的(Handler发送的Message,Message会持有Handler引用)Handler#handleMessage(Message msg)方法
系统创建Looper部分代码ActivityThread#main()
public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); ............ Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
从Looper.loop()被调用起,UI线程就进入了消息的不断轮询中,一旦有新的消息过来,系统就会执行,android应用程序就这样跑起来了,这也是为什么UI线程中创建Handler不需要调用Looper#prepare()的原因
切换线程
切换线程实际上就是调用位置的切换而已,Handler切换线程的机制最终原因是handleMessage方法的调用位置的切换,比如主线程。
handler将线程切换到主线程
handler将自己的引用间接被Looper持有,当Looper在主线程调用loop()方法时,该方法会取出handler并调用其handleMessage()方法,相当于切换到主线程
handler--Looper--MessageQueue的关系
这三者构成Handler通信机制
MessageQueue内部使用单链表来存储信息Message,Handler发送的Message全部都添加到了MessageQueue中,Looper#loop()方法通过调用MessageQueue#next()方法不断遍历链表中的Message,当取得符合的Message后,通过Message持有的Handler对象引用调用Handler#handleMessage方法,如此,Looper#loop()在哪个线程调用的,handleMessage方法就切换到哪个线程了。
简单代码模拟实现handler切换线程到主线程的思想
Handler:
public class MHandler {
MLooper mLooper;
public MHandler(MLooper mLooper) {
this.mLooper = mLooper;
}
public void sendMessage(MMessage msg) {
//让msg保持MHandler的引用,同时让mLooper保持msg的引用,并在mLooper获取msg中的handler回调handleMessage,
// 这样就能在mLooper的线程中调用handleMessage,相当于切换了线程
msg.mHandler = this;
mLooper.msg = msg;
//主线程中的looper调用loop()后会处于阻塞状态,获取到msg同时间接获取到handler,此时msg不为null,
// looper#loop()程序继续进行调用handlerMessage
}
public void handleMessage(MMessage msg) {
}
}
Message(这里只模拟一个消息,所以就用Message代替MessageQueue)
/** * Created by zz on 16/3/6. * 如果只是切换线程的话,只需要让Looper等待Handler的引用到来然后在ui线程调用就可以 * ,这里是为了模拟Handler发送信息,所以添加了一个Message */ public class MMessage { MHandler mHandler; String msg; }
Looper:
public class MLooper { //相当于把MessageQueue简化成一个msg了 MMessage msg; public void loop() { while(msg == null) { //如果looper没有获取到msg,就让它一直等待 } msg.mHandler.handleMessage(msg); } }
调用代码
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_2); tv = (TextView) findViewById(R.id.activity2_text); final MLooper looper = new MLooper(); tv.setClickable(true); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { MHandler handler = new MHandler(looper) { @Override public void handleMessage(MMessage msg) { super.handleMessage(msg); //主线程回调 tv.setTextSize(64); } }; MMessage msg = new MMessage(); msg.msg = "msg"; //子线程发送 handler.sendMessage(msg); } }.start(); //在主线程调用,切换了线程 looper.loop(); } }); }
效果就是将一个TextView的字体大小由32sp变大到64sp,就不演示了