Android 线程间交互、handler 及相关
1,先说线程间的交互
1.1.1 synchronized 相关
如图中:A和C, synchronized 关键字加在方法外,那么他们就会共享一个线程锁(及这个对象),不管是执行A还是C方法都要,等之前的A或C方法执行完毕。
如图中:B , synchronized 关键字加在方法内的代码块上,那么小括号中的参数(及红色的成员变量)就是识别是否同步的标示,也就是说‘那些要执行synchronized代码块的参数一致的线程,会进行排队等候。’
1.1.2 synchronized 保护的是什么?
凡是会被线程共享的都需要被保护(只读的不算)。
一旦决定做保护,就要在所有地方加保护。
1.2.1 一个线程调用另一个线程(启动,终结)
thread.start(); 开启线程。
thread.stop(); 结束线程。(不建议使用,造成的后果不可预知)。
thread.interrupt(); 打断线程。代替stop方法,
thread.interrupt()实际上是 给线程进行 线程中断的标记,实际上没有打断。
那么到底如何安全的结束线程呢?
之前使用thread.interrupt()对线程进行了标记
- 1 在需要打断的地方 使用if(thread.isInterrupted())~~配合 return;
- 2 在需要打断的地方 使用if(Thread.Interrupted())~~配合 return; 和上面的区别是,当标记为true时变成false ,为false时还是 false;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <1_000_000 ; i++) {
if (Thread.interrupted()){
//执行打断线程的收尾工作
return;
}
}
}
});
1.2.2 InterruptedException 相关
new Thread(){
@Override
public void run() {
try {
sleep(2000);
} catch (InterruptedException e) {
//主动打断后的处理
e.printStackTrace();
}
InterruptedException 一定是在你主动调用
thread.interrupt()后才会触发
例如在一个线程睡眠时,想要打断睡眠,就可以主动调用 thread.interrupt() 这样就会触发 InterruptedException。
扩展 SystemClock.sleep(2000); 安卓中的睡眠不用抛异常
所以高手都是使用 thread.interrupt() 来打断线程的
1.2.3 wait() 与 notify()
- wait() 会让线程“让开路”去一边等待,并记录当前代码运行位置。 待下一个线程调用notify()后,从记录的位置恢复运行。
场景:比如 等待初始化完毕在进行下一步。
private String testString;
private synchronized void printString(){
while(testString==null){
try {
wait(33);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("String:"+testString);
}
private synchronized void initString(){
testString = "zhangyuge";
notify();// notifyAll();
}
1.2.4 Thread.join 与 Thread.yield();
- Thread.join
表示 threadA 想停止自己等待 threadB 完成后在执行自己
threadA = new Thread() {
run{
threadB.join
}
}
- Thread.yield()
表示在同等优先级的线程间 稍稍让一下(自己停一下,让别人先执行)
2.1 Handle 机制
本质:在某个指定的运行中的线程中 运行代码。
2.1.1 ThreadLocal
ThreadLocal threadnumber;
threadnumber 的类型取决于 泛型中的类型;
new Thread(){
@Override
public void run() {
threadnumber.set(1)
threadnumber.get() // 1
}
}
new Thread(){
@Override
public void run() {
threadnumber.set(2)
threadnumber.get() // 2
}
}
在两个线程中 threadnumber.set get的值都互不干涉。原理类似与一个 Map存取值。
Handle 也就使用了 ThreadLocal的原理,这里ThreadLocal 泛型为Looper 这样,在每个线程中 getLooper是返回的都是自己的Looper了,每个线程之间的looper互不影响。
- HandlerThread
run 方法
looper.prepare();//创建并保存 Looper对象
looper.loop();//启动循环(检查和执行任务/ 消息) - Looper
循环
3.Handler
添加消息、制定消息的助理方案
这三者相互配合,就可在指定的线程 插入要执行的代码了。
3 AsyncTask 内存泄露
( 解决: static + 软引用)
提到内存泄露我们先提一下【回收】(因为泄露本质是某个对象该回收时无法被回收导致的)
如何判断一个对象是否 应该被回收呢?
- 不是看 这个对象有多少引用,而是看这个对象是否有来自 GarBage Collector Root 直接或间接的指向,有的话不会被回收。
- 什么是GC ROOT呢 (分三类)
- 静态对象
- 运行中的线程
- 本地的对象
AsyncTask 之所以会内存泄露,并不是因为AsyncTask 特殊。
比如说一个Activity 中 new Thread 做while 循环打印,当activity退出时,thread 会持一直持有他的引用, 导致内存泄露。
所以导致内存泄露的是线程没处理好,那么在AsyncTask 中导致内存泄露也是因为【线程】。
结论: 使用 AsyncTask 或者线程 快速结束就没问题,只要使用线程就记得要将他关闭。
比较 Executors 、AsyncTask 、Handle Thread
能用 Executors 就用 因为简单。
当需要将 后台任务,提到前台时使用 AsyncTask Handle。
最后在补充一下 Executors 的使用
Executors 方便了我们使用线程池。
- 同Executors 拿到线程池 有4中方法;
- newFixedThreadPool()
该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。 超出的线程会在队列中等待。
- newCachedThreadPool()
该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。如果没有线程不够用则会一直创建,有空闲线程则会复用。
- newSingleThreadExecutor()
顾名思义,返回只有一个线程的线程池。多余的任务会存在队列中等待执行
- newScheduledThreadPool()
返回一个可以固定线程个数和设置线程延迟执行时间,执行周期 的线程池
好了,来看下具体使用方式:
第一种的代码
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Log.i("thread", Thread.currentThread().getName());
}
}, 5000,3000, TimeUnit.MILLISECONDS);
第四种的代码
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Log.i("thread", Thread.currentThread().getName());
}
}, 5000,3000, TimeUnit.MILLISECONDS);