前言
把一个相对耗时且数据操作复杂的任务分割成多个小的操作,然后分别运行在多个线程上,这能够提高完成任务的速度和效率。在多核CPU的设备上,系统可以并行运行多个线程,而不需要让每个子操作等待CPU的时间片切换。例如,如果要解码大量的图片文件并以缩略图的形式把图片显示在屏幕上,当你把每个解码操作单独用一个线程去执行时,会发现速度快了很多。
本篇博客总结本人在Android学习中所遇到的线程相关问题.
一.线程介绍
Thread
和Runnable
只是两个基本的线程类,通过他们能发挥的作用有限,但是他们是强大的Android线程类的基础类,例如Android中的HandlerThread
, AsyncTask
和IntentService
都是以它们为基础。Thread
和Runnable
同时也是ThreadPoolExecutor
类的基础。ThreadPoolExecutor
类能自动管理线程和任务队列,甚至可以并行执行多个线程。
1.1实现线程的方法
1. 继承`Thread`类
2. 实现`Runnable`接口
3. 实现`callable`接口
下面简单记录如何使用这三种方法实现线程,重点记录第三种方法.
1.1.1 继承Thread
类
private class DownloadThread extends Thread {
...
@Override
public void run() {
// 把当前的线程变成后台执行的线程
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
/*
* 把你想要在线程中执行的代码写在这里
*/
...
}
}
1.1.2 实现Runnable
接口
private class DownloadThread implements Runnable {
...
@Override
public void run() {
// 把当前的线程变成后台执行的线程
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
/*
* 把你想要在线程中执行的代码写在这里
*/
...
}
}
在一个类里,Runnable.run() 包含执行了的代码。通常在Runnable 中执行任何操作都是可以的,但需要记住的是,因为Runnable 不会在UI线程中运行,所以它不能直接更新UI对象,例如View 对象。
注意:
在Runnable.run())方法的开始的地方通过调用参数为THREAD_PRIORITY_BACKGROUND 的Process.setThreadPriority()方法来设置线程使用的是后台运行优先级。
1.1.3 实现callable
接口
首先看看源码中对callable
和runnable
的定义
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;
}
public interface Runnable {
/**
* Starts executing the active part of the class' code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
public void run();
}
下面说说两者之间的区别:
Runnable和Callable的区别:
- Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
- Callable规定的方法是call(),Runnable规定的方法是run()
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)
- call方法可以抛出异常,run方法不可以
- 运行Callable任务可以拿到一个
Future
对象,表示异步计算的结果。它提供了检查计算是否完成的方法isDone()
以等待计算的完成,并检索计算的结果get()
,取消计算cancle(boolean)
.通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。 - 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。
示例代码:
public class OneTask implements Callable<String> {//callable有个<V>,这个V就是call函数的返回值类型
private int id;
public OneTask(int id){
this.id = id;
}
@Override
public String call() throws Exception {//这儿可以抛出异常,而Runnable接口的run函数不可以
int i=5;
while (i>=0) {
System.out.println("Thread "+ id +" is working");
Thread.sleep(1000);
i--;
}
return "result of Test2 " + id; //Runnable接口的run函数是没有返回值的
}
}
测试类:
public class Test1 {
public static void main(String[] args) {
Callable<String> oneCallable = new OneTask(1);
FutureTask<String> ft= new FutureTask<String>(oneCallable);
//FutureTask<String>是一个包装器,它通过接受Callable<String>来创建,它同时实现了Future和Runnable接口
new Thread(ft).start();
while(!ft.isDone()){
try {
System.out.println("检查线程执行完了吗...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String result = "";
try {
result = ft.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
}
其中,FutureTask
扮演监督的角色,主线程通过不断询问实现Callable的类对应的线程是否执行完毕,最后可以得到返回的结果.
其中Future
源码如下:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
*/
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
这种实现线程的方法相对于前两者两说,它可以对线程任务进行部分操作,比如:拿到线程执行的结果,在外界取消任务的执行,在外面查询任务是否之行结束等.
1.2 线程的生命周期
先来张图解:
- 线程的状态分为如下几种:可执行状态(Runnable),执行状态(Running),阻塞状态(Blocked),销毁状态(Dead).其中阻塞状态由于阻塞原因的不同,又分为三种阻塞:等待式阻塞,同步式阻塞,其他阻塞.
- 等待式阻塞:运行的线程调用了
wait()
方法. - 同步式阻塞:运行的线程在获取对象同步锁时,被别的线程占用,则JVM会把该线程放入锁池.
- 其他阻塞:由于
sleep()
,join()
或者I/O请求(比如,InputStream.read()
),JVM将线程置为阻塞状态,结束后在调用. sleep()
和wait()
方法的同在于:
- sleep()方法是Thread的静态方法,而wait()方法是Object类中的成员方法;
- sleep()调用的结果是,线程暂停执行;wait()执行的结果是,线程挂起;
- 两者最大的区别是:sleep()方法不释放同步锁,而wait()方法是释放同步锁的.sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态;而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备;
下面给出sleep()和wait()的测试代码以及测试结果:
public class TestD {
public static void main(String[] args) {
new Thread(new Thread1()).start();
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run(){
synchronized (TestD.class) {
System.out.println("enter thread1...");
System.out.println("thread1 is waiting...");
try {
//调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
TestD.class.wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread1 is going on ....");
System.out.println("thread1 is over!!!");
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run(){
synchronized (TestD.class) {
System.out.println("enter thread2....");
System.out.println("thread2 is sleep....");
//只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
TestD.class.notify();
//==================
//区别
//如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify()
//方法,则线程永远处于挂起状态。
try {
//sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
//但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
//在调用sleep()方法的过程中,线程不会释放对象锁。
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread2 is going on....");
System.out.println("thread2 is over!!!");
}
}
}
}
测试结果:
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
thread1 is going on ....
thread1 is over!!!