Android多线程应用

Android中提供了如下几种实现多线程的方法: java

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(); ide

 

2. AsyncTask 异步任务: 函数

一个实例仅执行一次,屡次execute会抛出异常。因此,要执行几个任务就new几个实例。内部会建立一个进程做用域的线程池来管理要运行的任务。

2.3平台之前调用execute,全部的任务并发执行,内部的线程池限制是5个,后来默认串行执行,只能调用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()中,检测该状态位来判断是否继续运行。

在 Java 中,非静态匿名内部类会持有其外部类的隐式引用,该引用会致使 Activity 被保留,而不是被垃圾回收机制回收。将线程类(Thread,AsyncTask)声明为私有的静态内部类避免了 Activity context 的内存泄漏问题,但在配置发生改变后,线程仍然会执行。缘由在于,DVM 虚拟机持有全部运行线程的引用,不管这些线程是否被回收,都与 Activity 的生命周期无关。运行中的线程只会继续运行,直到Android 系统将整个应用进程杀死。在退出当前Activity 前使用 onDestroy() 方法结束运行中线程是个不错的选择。

private MyTask task;
private static int i = 1;

private static class MyTask extends AsyncTask<Void, Void, Integer>{
		@Override
		protected Integer doInBackground(Void... params) {
			while(!isCancelled()) {
				i++;
				Log.d("i = ", String.valueOf(i));
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return null;
		}
	}

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	task = new MyTask();
	task.execute();
}
	
@Override
protected void onDestroy() {
	super.onDestroy();
	if(task!=null && task.getStatus()==Status.RUNNING) {
		task.cancel(true);
	}
}


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用于表示当任务已经开始执行时,是否须要尝试中断执行该任务的线程。(注意:这只是表示任务是否可以接收中断,而不是表示任务是否能检测并处理中断)
因此仍是得在本身的任务中经过断定中断标志位来结束任务。

建议:
与主线程有交互时用AsyncTask,不然就用Thread。
当有须要大量线程执行任务时,必定要建立线程池,好比批量下载图片或文件。
对于想要当即开始执行的异步任务,要么直接使用Thread,要么单首创建线程池提供给AsyncTask,默认的AsyncTask不必定会当即执行你的任务。


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方能继续执行。

相关文章
相关标签/搜索