Android中提供了以下几种实现多线程的方法:
1. Thread+Handler实现多线程:
不要在自己新建的Thread里对UI进行操作。可以使用Handler与主线程进行交互。有以下两种使用方式:
<1>. 发送消息:
// Handler定义在哪个线程,就被绑定在该线程 Handler handler = new Handler() { public void handleMessage(Message msg) { super.handleMessage(msg); // 在handler所在的线程上处理消息,如果handler所在的线程是主线程,可以更新UI } }; // 新建线程Thread new Thread(new Runnable(){ public void run() { while(!Thread.currentThread().isInterrupted()) { Message msg = handler.obtainMessage(); msg.what = XX; msg.arg1 = XX; msg.obj = XX; msg.sendToTarget(); // 在新线程中分发Message对象到handler所在的线程 } } }).start();
<2>. 发送Runnable对象:
// Handler定义在哪个线程,就被绑定在该线程 Handler handler = new Handler(); // 新建线程Thread new Thread(){ public void run(){ while(!Thread.currentThread().isInterrupted()) { handler.post(new Runnable(){ // 在新线程中分发Runnable对象到handler所在的线程中 public void run(){ // 运行在handler所在的线程,如果handler所在的线程是主线程,可以更新UI } }); } } }.start();// 新建了线程
以上例子都是在主线程中创建Handler,在子线程中引用。如果要在自己的Thread中创建Handler来与主线程交互的话,必须在new Handler前加上Looper.prepare(),在创建后加上Looper.loop()。原因:只有主线程默认带了消息队列,自定义线程是不带MessageQueue和Looper的。
<3>.Thread中断问题:
虽然有stop()接口,但是不安全,会发生异常。所以,通过调用interrupt()设置中断标示,在Thread的run()函数中,如果有循环语句,在循环中通过判定isInterrupted(),如果为true,主动退出run()函数即可结束线程,达到中断的目的。
<4>. 一组线程的执行是无序的,可以实现协同工作,也可以各自独立工作。
// 多个线程共享同一个TaskRun的实例,即实现协作工作。
TaskRun task = new TaskRun(); //TaskRun实现Runnable接口的run()函数
new Thread(task,"t1").start();
new Thread(task,"t2").start();
// 多个线程各自初始化各自的实例,即各自做各自的工作。
MyThread myT1 = new MyThread(); //MyThread继承了Thread,自己实现run()函数
MyThread myT2 = new MyThread();
myT1.start();
myT2.start();
2. AsyncTask 异步任务:
一个实例仅执行一次,多次execute会抛出异常。所以,要执行几个任务就new几个实例。
2.3平台以前调用execute,所有的任务并发执行,后来默认串行执行,只能调用executeOnExecutor((ExecutorService)Executors.newCachedThreadPool())实现并发,这里没有使用默认的线程池,而使用自定义的。
适用于短时间的多线程任务(几分钟)。如果要完成长时间的运行任务,最好使用Executor,ThreadPoolExecutor,FutureTask。
<1>.AsyncTask<Params, Progress, Result>
是抽象类,定义了三种泛型类型,分别代表“启动任务执行的输入参数”(如HTTP请求的URL)、“后台任务执行的进度”、“后台计算结果的类型”。如果没有被使用,可以用Void代替。
<2>.AsyncTask定义的方法:
execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。
doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。不能更新UI,只能在执行过程中调用publishProgress(Progress... values)来更新进度信息。
onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时被执行,将进度信息更新到UI组件上。
onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
<3>.终止线程:
cancel()终止后台线程的运行,可以先用isCanceled()来询问,避免重复叫停。
执行cancel(),将会回调onCanceled(),同时onPostExecute()就不会被回调。如果想做到及时中断,在doInBackground()中,检测该状态位来判断是否继续运行。
3. 线程池:
Java通过Executors提供的四种线程池,分别为:
<1>. newCachedThreadPool创建一个可缓存线程池,调用 execute 将重用以前构造的线程(如果线程可用),若无可回收,则新建线程。
有IDLE机制,线程如果超过TIMEOUT(60s)不活动,其会自动被终止。通常用于执行一些生存期很短的异步型任务。
<2>. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
没有IDLE机制,在某个线程被显式地关闭之前,池中的线程将一直存在。多数针对一些很稳定很固定的正规并发线程。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
<3>. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
以下是3种定时器的写法与比较:
Handler.postDelayed实际上是运行在主线程的,也可以新建一个Thread来发送Runnable。
在Timer机制中,只封装了一个线程来执行定时任务,而且有时有异常发生。
ScheduledExecutorService比Timer更安全,功能更强大,是最优化方案。
private static final int REGUEST_MESSAGE_TIME = 1000*60*60*2;//2小时 //使用Handler作为定时器,实际上Runnable还是在主线程中运行 private Handler mTimerHandler = new Handler(); private Runnable mTimerRunnable = new Runnable() { @Override public void run() { // 2小时后执行一次Runnable msgTimerHandler.postDelayed(this, REGUEST_MESSAGE_TIME); } }; // Timer + TimerTask定时器,在Timer机制中,只有一个线程来执行定时任务 private Timer mTimer = null; private TimerTask mTimerTask = new TimerTask() { @Override public void run() { } }; // 使用线程池的方式支持定时线程任务 private ScheduledExecutorService mScheduledExecutorService = null; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 马上执行Runnable msgTimerHandler.post(mTimerRunnable); mTimer = new Timer(); // 每隔2小时执行一次TimerTask,循环任务 mTimer.schedule(mTimerTask, 0, REGUEST_MESSAGE_TIME); // 该线程池可以容纳5个线程 mScheduledExecutorService= Executors.newScheduledThreadPool(5); // 马上执行Runnable ,然后每隔2小时执行一次Runnable mScheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { } }, 0, 2, TimeUnit.HOURS); } protected void onDestroy() { super.onDestroy(); // 清除以该Handler为target的所有Message和Callback,防止内存泄露 msgTimerHandler.removeCallbacksAndMessages(null); if ( mTimer != null ) { //不但可以结束当前schedule,连整个Timer的线程都会结束掉 mTimer.cancel(); mTimer = null; } //关闭线程池队列中的所有等待任务 mScheduledExecutorService.shutdown(); }
<4>. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 不用考虑同步的问题。
没有IDLE机制。可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。
好处:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
开启线程的两种方法:
Future<?> submit(Runnable task) /*用Future来判断线程运行状态*/
void execute(Runnable command) /*无法判断线程是否成功完成*/
关闭线程池的方法:
shutdown() 停止接受新任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。
shutdownNow() 试图停止所有正在执行的活动任务(调用对应线程的interrupt(),设置中断标志位而已),暂停处理正在等待的任务,并返回等待执行的任务列表。
中断线程池某个任务:
向ExecutorService提交任务调用submit方法之后,返回值是个Future对象,可以使用这个对象的cancel方法来取消对应任务。
如果任务还在等待队列中,会直接取消掉。
如果任务已经执行了,cancel的boolean值参数mayInterruptIfRunning用于表示当任务已经开始执行时,是否需要尝试中断执行该任务的线程。(注意:这只是表示任务是否能够接收中断,而不是表示任务是否能检测并处理中断)
所以还是得在自己的任务中通过判定中断标志位来结束任务。
4. 访问临界资源时,使用线程同步机制:
synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等。
5. 处理线程阻塞中断:
在执行涉及线程调度的阻塞调用时(例如wait、sleep和join),如果发生中断,被阻塞线程会“尽可能快的”抛出InterruptedException。因此,我们就可以用下面的代码框架来处理:
try {
//wait、sleep或join
}
catch(InterruptedException e) {
//某些中断处理工作
}
<1>.sleep()静态方法
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。
使当前线程进入阻塞状态,在指定时间内不会执行。其他线程在此期间可以获得运行机会。线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。何时运行还要看线程池如何调度。
<2>.wait()方法
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
wait()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
<3>.yield静态方法
暂停当前正在执行的线程对象,并执行其他线程。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
<4>.join方法
保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
如在t1线程中调用t2.join(),则需要t2线程执行完后t1方能继续执行。