Android 中线程的使用
Android官网文档->https://developer.android.com/guide/components/processes-and-threads.html?hl=zh-cn#Threads
应用启动时,系统会为应用创建一个线程,称为主线程;它负责UI的绘制以及UI的事件响应交互,也称为UI线程; 系统不会为每个组件实例创建单独的线程,同一进程中的所有组件都在主线程实例化,并且每个组件的资源调用都由主线程分配,因此响应系统回调都在主线程进行。 因为主线程要处理UI的绘制及事件的交互,所以主线程中不能进行耗时的操作(网络访问,数据库操作),一旦主线程进行耗时操作就会出现阻塞,UI事件就没办法响应了,就会出现ANR,这是非常不友好的。
Android UI是非线程安全的,所以关于UI的操作只能在UI线程操作,所以Android单线程模式必须遵守两条规则
为了保证应用的顺畅,所有耗时的操作都在工作线程中进行。
遵循上述的两条规则,不能再UI线程之外的线程访问UI,但是网络访问结果是在工作线程,要将结果填充到UI中怎么办呢,Android提供了几种方法在工作线程中访问UI
还有一种方式 AsyncTask;
有两种方式创建一个线程。
/**
* 通过继承Thread 创建一个Thread
*/
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
/**
* 重写run方法 JVM会自动调用此方法
*/
@Override
public void run() {
// logic code
}
/**
* 重载(Overload)run()方法 和普通的方法一样,并不会在该线程的start()方法被调用后被JVM自动运行
*
* @param str
*/
public void run(String str) {
Log.e("run", str + "");
}
}
实现 Runnable接口
/**
* 通过实现 Runnable 创建一个线程
*/
class MyRunnable implements Runnable {
/**
* JVM会自动调用此方法
*/
@Override
public void run() {
//logic code
}
}
继承Thread方式
MyThread thread0 = new MyThread();
thread0.start();
实现 Runnable方式
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
thread1.start();
其实这块主要是围绕着接口和抽象类的区别以及一些设计原则而言的。
Thread类也是实现了Runnable接口
参考资料
关于多线程资源共享,多线程并发操作有随机性,不能保证每个线程都顺序的去访问某个资源,在多个线程同时去访问一个资源的时候要进行资源的同步.
经典的卖票例子
资源共享,多个线程并发执行访问同一个资源,才是共享的资源。
票
/**
* 票
*/
class Tickets {
int num = 5;
public int get() {
return num;
}
public void set(int num) {
this.num = num;
}
}
卖票程序
/**
* 卖票 程序
*/
class SaleRunnable implements Runnable {
private Tickets tickets;
public SaleRunnable(Tickets tickets) {
this.tickets = tickets;
}
@Override
public void run() {
while (tickets.get() > 0) {
Log.e(Thread.currentThread().getName(), "销售第" + tickets.get() + "张,剩余" + (tickets.get() - 1) + "张");
tickets.set(tickets.get() - 1);
try {
//延迟 1000
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e(Thread.currentThread().getName(), "没票了");
}
}
卖票
//票资源
Tickets tickets = new Tickets();
SaleRunnable saleRunnable = new SaleRunnable(tickets);
//开三个窗口使用同一程序去卖票
new Thread(saleRunnable, "窗口零").start();
new Thread(saleRunnable, "窗口一").start();
new Thread(saleRunnable, "窗口二").start();
卖票结果
多线程并发执行的结果就是 多个窗口同时售票,同一时间销售掉了同一个票,尴尬了,一个票卖了多次
08-22 14:23:41.066 E/窗口零: 销售第5张,剩余4张
08-22 14:23:41.066 E/窗口一: 销售第4张,剩余3张
08-22 14:23:41.076 E/窗口二: 销售第3张,剩余2张
08-22 14:23:42.066 E/窗口零: 销售第2张,剩余1张
08-22 14:23:42.076 E/窗口二: 销售第2张,剩余1张
08-22 14:23:42.076 E/窗口一: 销售第2张,剩余1张
08-22 14:23:43.076 E/窗口一: 没票了
08-22 14:23:43.076 E/窗口二: 没票了
08-22 14:23:43.076 E/窗口零: 没票了
08-22 14:24:29.866 E/窗口零: 销售第5张,剩余4张
08-22 14:24:29.866 E/窗口一: 销售第4张,剩余3张
08-22 14:24:29.866 E/窗口二: 销售第3张,剩余2张
08-22 14:24:30.866 E/窗口零: 销售第2张,剩余1张
08-22 14:24:30.866 E/窗口二: 销售第1张,剩余0张
08-22 14:24:30.866 E/窗口一: 销售第2张,剩余1张
08-22 14:24:31.866 E/窗口零: 没票了
08-22 14:24:31.866 E/窗口二: 没票了
08-22 14:24:31.866 E/窗口一: 没票了
08-22 14:24:51.906 E/窗口二: 销售第5张,剩余4张
08-22 14:24:51.906 E/窗口一: 销售第4张,剩余3张
08-22 14:24:51.906 E/窗口零: 销售第3张,剩余2张
08-22 14:24:52.906 E/窗口二: 销售第2张,剩余1张
08-22 14:24:52.906 E/窗口一: 销售第2张,剩余1张
08-22 14:24:52.916 E/窗口零: 没票了
08-22 14:24:53.916 E/窗口二: 没票了
08-22 14:24:53.916 E/窗口一: 没票了
Java 同步块(synchronized block)用来标记方法或者代码块是同步的
java中每个对象都对应于一个称为“互斥锁”的标志,这个标志用来保证在任何时刻,只能有一个线程访问该对象。 如果系统中的资源当前没有被使用,线程可以得到“互斥锁”,即线程可以得到资源的使用权。 当线程执行完毕后,他放弃“互斥锁”,如果一个线程获得“互斥锁”时,其余的线程就必须等待当前线程结束并放弃“互斥锁”。 在java中,提供了关键字synchronized来实现对象的“互斥锁”关系。 当某个对象或方法用关键字synchronized修饰时,表明该对象或方法在任何一个时刻只能有一个线程访问。 如果synchronized用在类的声明中,表明该类中的所有方法都是synchronized的。
在这个例子中,我们只需要将“票”这个资源同步即可
多个线程都是访问的这一个实例,所以同步这个实例方法,就可以了;
/**
* 卖票 程序
*/
class SaleRunnable implements Runnable {
private Tickets tickets;
public SaleRunnable(Tickets tickets) {
this.tickets = tickets;
}
@Override
public void run() {
while (true) {
if (sale()) {
break;
}
try {
//延迟 1000
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e(Thread.currentThread().getName(), "没票了");
}
private synchronized boolean sale() {
if (tickets.get() > 0) {
Log.e(Thread.currentThread().getName(), "销售第" + tickets.get() + "张,剩余" + (tickets.get() - 1) + "张");
tickets.set(tickets.get() - 1);
return false;
}
return true;
}
}
或者
/**
* 卖票窗口
*/
class SaleThread extends Thread {
private Tickets tickets;
public SaleThread(Tickets ti, String name) {
super(name);
tickets = ti;
}
@Override
public void run() {
while (true) {
//同步这个资源
synchronized (tickets) {
if (tickets.get() > 0) {
Log.e(Thread.currentThread().getName(), "销售第" + tickets.get() + "张,剩余" + (tickets.get() - 1) + "张");
tickets.set(tickets.get() - 1);
} else {
break;
}
}
try {
//延迟 1000
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e(Thread.currentThread().getName(), "没票了");
}
}
学习资料
当一个线程的实例被创建即使用new
关键字后台Thread类或者其子类创建一个线程对象后,此时该线程就处于新生状态,处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的(not alive)
通过调用线程实例的start()
方法来启动线程使线程进入就绪状态(runnable);处于就绪状态的线程已经具备了运行条件,但还没被分配到CPU就是不一定会被立即执行,此时处于线程就绪队列,等待线程为期分配CPU,等待状态不是执行状态;此时线程是活着的(alive);
一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程run()
方法才开始被执行;在运行状态的线程执行自己的run()方法中的操作,知道调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的就绪状态;此时线程是活着的(alive);
通过调用join()
,sleep()
,wait()
或者资源被占用使线程处于阻塞(blocked)状态;处于Blocked状态的线程仍然是活着的(alive);
当一个线程的run()
方法运行完毕或被中断或被异常退出,该线程到达死亡(dead)状态。此时可能仍然存在一个该Thread的实例对象,但该Thread已经不可能在被作为一个可被独立执行的线程对待了,线程的独立的call stack已经被dissolved。一旦某一线程进入Dead状态,他就再也不能进入一个独立线程的生命周期了。对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime exception)的异常;处于Dead状态的线程不是活着的(not alive)。
学习资料
Java中常规的通信方式这里我就不说了,看一下Android的消息机制
Java常规的通信方式传送门->http://ifeve.com/thread-signaling
Android中的消息机制可以用于线程间通信也可用于在各个组件间通信,这里只总结一下怎么在线程间使用
消息机制中重要的API
运行机制
在哪个Thread中创建Handler,默认情况下Handler就会获取哪个线程中的Looper(前提是Looper创建好了);handler发送消息就是将消息发送到了自己持有的这个Looper对象里;
Looper内有一个MessageQueue,消息就存放在队列里,一旦Looper的loop()
方法被调用就会开启无限循环模式,一直循环遍历这个队列,从中取Handler发送的消息,没有消息就阻塞;一旦有消息就唤醒线程取出来;
从MessageQueue中取出的消息,会调用本身target持有的handler实例来处理这个消息;
综上所述,线程间通信handler就可以实现;
主线程给工作线程发消息
想要在主线程给工作线程发消息,我们就得持有在工作线程中创建的handler; 而创建handler之前必须先初始化一下Looper对象; handler创建完之后就开启Looper的无限循环来等待消息
创建一个线程并创建一个handler
Handler handlerA = null;
class ThreadA extends Thread implements Handler.Callback {
public ThreadA() {
super("ThreadA");
}
@Override
public void run() {
//创建此线程的Looper对象 ,一个线程只能有一个,所以此方法只能调用一次
Looper.prepare();
handlerA = new Handler(this);
//开始循环遍历
Looper.loop();
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100:
Log.e("handleMessage", Thread.currentThread().getName() + ";src->" + msg.obj);
break;
}
return false;
}
}
使用handlerA发送消息
//创建一个消息
Message msg = new Message();
msg.what = 100; //标识你这消息想要干啥
msg.obj = Thread.currentThread().getName();
/**
* 因为handler 里持有所在线程的Looper,所有handler发送的消息会在所在线程中执行
*/
//发送给A线程
handlerA.sendMessage(msg);
看一个log 处理线程是ThreadA,消息来源是Main线程
08-23 16:26:02.609 E/handleMessage: ThreadA;src->main
工作线程发给主线程
与上面的同理,想要给主线程发送消息,拿到主线程的handler就可以了;
因为点击事件是在UI线程中响应的,所以想让工作线程给主线程发送一个消息就麻烦一点,我这里为了测试做了个中转,先给B线程发送一个信号,B接到这个信号就给主线程发消息
class ThreadB extends Thread implements Handler.Callback {
public ThreadB() {
super("ThreadB");
}
@Override
public void run() {
Looper.prepare();
handlerB = new Handler(this);
Looper.loop();
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100:
Log.e("handleMessage", Thread.currentThread().getName() + ";src->" + msg.obj);
break;
case 101: //给main发送一个消息
Message message = new Message();
message.what = 100;
message.obj = Thread.currentThread().getName();
handler.sendMessage(message);
break;
}
return false;
}
}
在主线程创建的handler
/**
* Main 的handler
* 程序启动的时候就为Main线程创建了Looper
*/
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e("handleMessage", Thread.currentThread().getName() + ";src->" + msg.obj);
return false;
}
});
主线程的handler创建时没有提前创建Looper也没有调用Looper的loop()
方法,是因为程序在启动的时候已经为主线程创建好了Looper,并且调用了loop()
,一直在等待消息
工作线程给工作线程发消息
跟上面两个一样,想给哪个线程发消息就要先拿到哪个线程的handler;我这里就不贴代码了;
学习资料
为啥使用线程池
ExecutorService
是一个接口,定义了线程池的方法。
public interface ExecutorService extends Executor {
void shutdown();//关闭线程池,不再接受新任务,当所有任务都执行完毕后,关闭线程池
List<Runnable> shutdownNow(); //关闭线程池,阻止等待任务启动并试图停止当前正在执行的任务,停止接收新的任务,返回处于等待的任务列表
boolean isShutdown(); //是否关闭
boolean isTerminated();//如果关闭后所有任务都已完成,则返回 true。注意,除非首先调用 shutdown 或 shutdownNow,否则 isTerminated 永不为 true。
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;//等待(阻塞)直到关闭或最长等待时间或发生中断,timeout - 最长等待时间 ,unit - timeout 参数的时间单位 如果此执行程序终止,则返回 true;如果终止前超时期满,则返回 false
<T> Future<T> submit(Callable<T> var1); //提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。该 Future 的 get 方法在成功完成时将会返回该任务的结果。
<T> Future<T> submit(Runnable var1, T var2); // 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功完成时将会返回给定的结果
Future<?> submit(Runnable var1);///提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> var1) throws InterruptedException; //执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。返回列表的所有元素的 Future.isDone() 为 true。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> var1, long var2, TimeUnit var4) throws InterruptedException; //执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。返回列表的所有元素的 Future.isDone() 为 true。
<T> T invokeAny(Collection<? extends Callable<T>> var1) throws InterruptedException, ExecutionException; //执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。一旦正常或异常返回后,则取消尚未完成的任务。
<T> T invokeAny(Collection<? extends Callable<T>> var1, long var2, TimeUnit var4) throws InterruptedException, ExecutionException, TimeoutException;
}
ThreadPoolExecutor
具体实现了ExecutorService
接口,提供了一系列的参数来配置线程池,熟悉ThreadPoolExecutor
可自定义线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
Executors.defaultThreadFactory()
即可Android通过Executors
为我们提供了几种线程池
方法 | 说明 |
---|---|
Executors.newCachedThreadPool() | 缓存线程池,线程数量不定,最大线程数为Integer.MAX_VALUE(相当于任意大),有新任务时会检查是否有空闲线程,没有则会创建线程,空闲线程超过60s会被回收,任何任务都会被立即执行,适合大量的耗时较少任务 |
Executors.newFixedThreadPool(int nThreads) | 固定型线程池,线程数量固定,只有核心线程并且无超时机制,当所有线程都执行任务时,新任务进入队列等待。能够快速响应请求 |
Executors.newScheduledThreadPool(int corePoolSize) | 调度型线程池,核心数量固定,非核心数量无限制,非核心线程一旦空闲立马回收。会根据Scheduled(任务列表)进行延迟执行,或者是进行周期性的执行.适用于一些周期性的工作 |
EExecutors.newSingleThreadExecutor() | 单例线程池,只有一个核心线程,所有任务都在这个线程中串行执行,不需要处理线程同步问题,在任意的时间段内,线程池中只有一个线程在工作… |
在ExecutorService
的方法中可以看到线程池除了可执行Runnable
接口还可以执行Callable<V>
接口,并且可以通过Future<V>
来感知线程状态和结果。
因为Runnable
的返回值为void
无法获取执行完毕后的结果 ,所以才有了Callable<V>
,可以返回一个结果
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Future<V>
定义了几种方法来感知线程状态和获取结果 ,可以理解为管理线程的。Future提供了三种功能:
public interface Future<V> {
/**
* 尝试取消执行此任务。 如果任务已经完成,已经被取消或由于某种其他原因而无法取消,则此尝试将失败。返回false。
* 当此方法调用时此任务尚未开始,则此任务不会被运行。返回true。
* 如果任务已经开始,则{mayInterruptIfRunning}参数确定执行此任务的线程是否应该中断,以试图停止该任务。
* 此方法返回后,对{@link #isDone}的后续调用将始终返回{@code true}。
* 如果此方法返回{@code true},则后续调用{@link #isCancelled}将始终返回{@code true}。
* @param mayInterruptIfRunning 线程在运行是否中断; 如果值为true表示中断正在进行的任务返回则返回true,值为false表示不中断返回false,如果任务无法取消,通常是因为它已经正常完成;
* {@code true} otherwise
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 如果在完成之前被取消 则返回 true,否则返回false
*
* @return {@code true} if this task was cancelled before it completed
*/
boolean isCancelled();
/**
* 任务已经完成 返回 true。
* 任务被取消,异常,或者正常完成都会返回 true。此方法返回结果表示任务是否允许完毕
* @return {@code true} if this task completed
*/
boolean isDone();
/**
* 获取任务完成的结果,如果任务没有执行完毕,则阻塞线程,直到拿到结果
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
V get() throws InterruptedException, ExecutionException;
/**
* 用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null
* @param timeout 指定等待时间
* @param unit 等待时间单位
* @return the 执行结果
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
因为Future
只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
。
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。 所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
public class FutureTask<V> implements RunnableFuture<V> {}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
ScheduledExecutorService
调度线程池 ,扩展了ExecutorService
,增加了几个方法用来执行周期性或者定时的任务
public interface ScheduledExecutorService extends ExecutorService {
/**
* 在给定延迟时间后执行一个单次的操作
* @param command 要执行的操作
* @param delay 延迟时间
* @param unit 延迟时间单位
* @return a ScheduledFuture 代表一个未完成的任务,可以通过get()获取null,还可以通过getDelay()获取剩余延迟时间
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if command is null
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
/**
* 在给定延迟时间后执行一个单次的 操作
* @param callable 单次操作
* @param delay 延迟时间
* @param unit 延迟时间单位
* @param <V> 返回结果类型
* @return a ScheduledFuture 可以通过get() 拿到结果,可以通过getDelay() 获取剩余延迟时间;
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if callable is null
*/
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
/**
* 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;
* 也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,
* 接着在 initialDelay + 2 * period 后执行,依此类推。
* @param command 要执行的操作
* @param initialDelay 首次执行延迟时间
* @param period 执行周期时间
* @param unit 时间单位
* @return a ScheduledFuture representing pending completion of
* the series of repeated tasks. The future's {@link
* Future#get() get()} method will never return normally,
* and will throw an exception upon task cancellation or
* abnormal termination of a task execution.
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if command is null
* @throws IllegalArgumentException if period less than or equal to zero
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
/**
* 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
* @param command 要执行的定期任务
* @param initialDelay 首次执行的延迟时间
* @param delay 再上一次任务执行完毕后下次要执行的延迟时间
* @param unit 时间单位
* @return a ScheduledFuture representing pending completion of
* the series of repeated tasks. The future's {@link
* Future#get() get()} method will never return normally,
* and will throw an exception upon task cancellation or
* abnormal termination of a task execution.
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if command is null
* @throws IllegalArgumentException if delay less than or equal to zero
*/
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
关于几种线程池的使用,代码都放在我的GitHub了。
学习资料
关于本次的Demo https://github.com/sky-mxc/AndroidDemo/tree/master/thread
没有涉及到的地方,欢迎补充。错误的地方,感谢指正