1.Hanlder:
Handler,Message,looper和MessageQueue构成了安卓的消息机制,handler创建后可以通过sendMessage将消息加入消息队列,然后looper不断的将消息从MessageQueue中取出来,回调到Hander的handleMessage方法,从而实现线程的通信。
从两种情况来说,第一在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列 ,消息队列是一个无限循环,为什么无限 循环不会ANR?
我们可以想想什么情况下会发生ANR,第一,事件没有得到处理,第二,事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR
另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
详解
1,Message 是线程之间传递的消息,它可以在内部携带少量信息,用于在不同线程之间交换数据。
2. Handler 是处理者,它主要用于发送和处理消息。 发送消息一般使用 handler 的 sendMessage()方法,处理消息会调用 handleMessage() 方法。
3. MessageQueue 是消息队列,它主要用于存放所有由 Handler 发送过来的消息,这部分消息会一直在消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象。
4. Looper 是每个线程中 MessageQueue /的管家, 调用 loop() 方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将其取出,并传递到 handleMessage()方法当中。每个线程中也只会有一个Looper对象。
内部实现:
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,其他线程
则无法获取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。当不同线程访问
同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后
再从数组中根据当前 ThreadLcoal 的索引去查找对应的 value 值
Looper
loop 方法是一个死循环,唯一跳出循环的方式是 MessageQueue 的 next 方法返回了 null。
当 Looper 的 quit 方法被调用时,Looper 就会调用 MessageQueue 的 quit 或者
qutiSafely 方法来通知消息队列退出,当消息队列被标记为退出状态时,它的 next 方法就会
返回 null。loop 方法会调用 MessageQueue 的 next 方法来获取新消息,而 next 是一个
阻塞操作,当没有消息时,next 会一直阻塞,导致 loop 方法一直阻塞。Looper 处理这条
消息: msg.target.dispatchMessage(msg),这里的 msg.target 是发送这条消息的 Handler
对象。
HandlerThread
HandlerThread 集成了 Thread,却和普通的 Thread 有显著的不同。普通的 Thread 主要用
于在 run 方法中执行一个耗时任务,而 HandlerThread 在内部创建了消息队列,外界需要
通过 Handler 的消息方式通知 HanderThread 执行一个具体的任务。
2.AsyncTask
AsyncTask 内部也是 Handler 机制来完成的,只不过 Android 提供了线程池来执行相应地任务,因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务,
比如 HTTP 请求,大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有 线程来执行其他的任务,导致的情形是会发生 AsyncTask 根本执行不了的问题。
因为asynctask实际上是一个线程池,最大只支持5个并发。如果有线程长时间占用,且没有空闲,则其他线程只能处于等待状态,会造成阻塞。
广播和EventBus区别
EventBus优点:开销小,代码优雅。将发送者和接受者解耦
在EventBus3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING)
普通的方法是先注册(register),再post,才能接受到事件;
如果你使用postSticky发送事件,那么可以不需要先注册,也能接受到事件,也就是一个延迟注册的过程。
粘性事件就是为了解决这个问题,通过 postSticky 发送粘性事件,这个事件不会只被消费一次就消失,而是一直存在系统中,直到被 removeStickyEvent 删除掉。
那么只要订阅了该粘性事件的所有方法,只要被register 的时候,就会被检测到,并且执行。
IntentService
Service运行在主线程 ,不能做耗时操作,在执行完毕之后,会放置,并不会自动关闭。
IntentService他运行在子线程中,可以做耗时操作,他会同步执行我们所有的任务,当所有任务执行完毕之后,自动关闭自己。
BindService和StartService区别
使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出了,服务仍然运行。
onCreate()- >onStartCommand()->startService()->onDestroy()
使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
onCreate()->onBind()->onUnbind()->onDestroy()
4.线程:
线程和进程的区别:进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。
一个进程内可拥有多个线程,进程可开启进程,也可开启线程。
一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。
介绍:线程:是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位. 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计 数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的 全部资源.
三种方式:
方式1:继承Thread类定义Thread类的子类,并重写该类的run()方法
注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.
方式2:实现Runnable接口
方式3:实现Callable接口
区别:
1,Runnable没有返回值,Callable可以返回执行结果,是个泛型和Future、FutureTask配合可以用来获取异步执行结果
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()方法得到,此方法会阻塞不能继续往下走
2,Callable接口的call()方法润许抛出异常;Runnable的run()方法只能在内部消化,不能往上继续抛
线程的状态(生命周期)
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的线程除CPU之外,其它的运行所需资源都已全部获得。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
阻塞的情况分三种:
1、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,包括锁标记及CPU,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入就绪状态。
3、其他阻塞:运行的线程执行sleep()或其他线程执行join()方法,或者发出了I/O请求时,让出CPU,JVM会把该线程置为阻塞状态,但是不会释放所占用的资源,锁标记。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
线程安全(多线程数据同步问题)
在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:
由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。
这里面,这个资源被称为:临界资源(也有称为共享资源)。
是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。
在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。
sleep 方法和 wait 方法有什么区别?
这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点
在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,
wait 方法会放弃这个对象的监视器
synchronized
synchronized是Java中的关键字,是一种同步锁,临界资源使用完毕之后会自动。它修饰的对象有