Android 多线程编程初探

出门莫恨无人随,书中车马多如簇。这篇文章主要讲述Android 多线程编程初探相关的知识,希望能为你提供帮助。
    Android  中的多线程其实就是  Java  SE 中的多线程,只是为了方便使用,android  封装了一些类,如 AsyncTask、HandlerThread 等,在日常的开发过程中,我们往往需要去执行一些耗时的操作,例如发起网络请求,考虑到网速等其他外在的因素,服务器可能不会立刻响应我们的请求,如果不将这条操作放到子线程中去执行,就会造成主线程被阻塞,今天我们就从多线程的基础来一起探讨
 
一、线程的基本用法
 
对于 Andorid 多线程来说我们最新接触到的就是 Thread 和 Runnable 通常我们如下来启动一个新的线程
 
1)继承自 Thread 来创建子线程
 
定义一个线程只需要新建一个类继承自 Thread,然后重写父类的 run 方法即可
 
[java]  view plain  copy  

  1. /** 
  2.   *  继承  Thread  创建子线程 
  3.   */   
  4. class  MyThread  extends  Thread  {   
  5.         @Override   
  6.         public  void  run()  {   
  7.                 //处理具体的逻辑   
  8.         }   
  9. }   

那么如何启动这个线程呢?只需要 new 出 MyThread 的实例,然后调用它的 start() 方法,这样 run() 方法中的代码就会运行在子线程,如下: 
 
[java]  view plain  copy  
  1. new  MyThread().start();    
  2)实现 Runnable 接口来创建子线程
 
继承方式耦合性高,更多时候我们会选择实现 Runnable 接口的方式来实现一个子线程,如下:
 
[java]  view plain  copy  
  1. /** 
  2.   *  实现  Runnable  接口创建子线程 
  3.   */   
  4. class  MyThread  implements  Runnable  {   
  5.         @Override   
  6.         public  void  run()  {   
  7.                 //处理具体的逻辑   
  8.         }   
  9. }   

如果使用这种写法,启动线程的方法也需要相应的改变,如下: 
 
[java]  view plain  copy  
  1. MyThread  myThread  =  new  MyThread();    
  2. new  Thread(myThread).start();    

Thread 构造函数接受一个 Runnable 参数,我们 new 出的 MyThread 正是一个实现了 Runnable 接口的对象,所以可以直接将它传入到 Thread 的构造函数里,接着就和上面的一样了,调用 Thread 的 start() 方法,run() 方法中的代码就会在子线程当中运行了 
 
3)直接使用匿名类来创建子线程
 
 
[java]  view plain  copy  
  1. new  Thread(new  Runnable()  {   
  2.         @Override   
  3.         public  void  run()  {   
  4.                 //处理具体的逻辑   
  5.         }   
  6. }).start();    

如果你不想再定义一个类去实现 Runnable 接口,也可以使用如上匿名类的方式来实现,这种实现方式也是我们最常用到的 
以上这些就是我们来创建子线程时使用的不方式,基本和  Java  中一样
 
4)Thread 和 Runnable 的区别
 
        实际上 Thread 也是一个 Runnable,它实现了 Runnable 接口,在Thread类中有一个 Runnable 类型的 target字段,代表要被执行在这个子线程中的任务,代码如下:
Thread 部分源码:
 
[java]  view plain  copy  
  1. public  class  Thread  implements  Runnable  {   
  2.         /*  What  will  be  run.  */   
  3.         private  Runnable  target;    
  4.         /*  The  group  of  this  thread  */   
  5.         private  ThreadGroup  group;    
  6.    
  7.         private  String  name;    
  8.         /* 
  9.       *  The  requested  stack  size  for  this  thread,  or  0  if  the  creator  did 
  10.       *  not  specify  a  stack  size.    It  is  up  to  the  VM  to  do  whatever  it 
  11.       *  likes  with  this  number;   some  VMs  will  ignore  it. 
  12.       */   
  13.         private  long  stackSize;    
  14.    
  15.         /** 
  16.           *  初始化  Thread  并且将Thread  添加到  ThreadGroup  中 
  17.           * 
  18.           *  @param  g 
  19.           *  @param  target 
  20.           *  @param  name 
  21.           *  @param  stackSize 
  22.           */   
  23.         private  void  init(ThreadGroup  g,  Runnable  target,  String  name,  long  stackSize)  {   
  24.                 java.lang.Thread  parent  =  currentThread();    
  25.                 //group  参数为空,则获取当前线程的才线程组   
  26.                 if  (g  ==  null)  {   
  27.                         g  =  parent.getThreadGroup();    
  28.                 }   
  29.                 this.group  =  g;    
  30.                 this.name  =  name;    
  31.                 //设置  target   
  32.                 this.target  =  target;    
  33.                 /*  Stash  the  specified  stack  size  in  case  the  VM  cares  */   
  34.                 this.stackSize  =  stackSize;    
  35.         }   
  36.    
  37.         public  Thread()  {   
  38.                 init(null,  null,  null,  0);    
  39.         }   
  40.    
  41.         public  Thread(Runnable  target)  {   
  42.                 init(null,  target,  null,  0);    
  43.         }   
  44.    
  45.         public  Thread(ThreadGroup  group,  Runnable  target)  {   
  46.                 init(group,  target,  null,  0);    
  47.         }   
  48.    
  49.         public  Thread(Runnable  target,  String  name)  {   
  50.                 init(null,  target,  name,  0);    
  51.         }   
  52.    
  53.         public  Thread(ThreadGroup  group,  Runnable  target,  String  name)  {   
  54.                 init(group,  target,  name,  0);    
  55.         }   
  56.    
  57.         public  Thread(ThreadGroup  group,  Runnable  target,  String  name,   
  58.                                     long  stackSize)  {   
  59.                 init(group,  target,  name,  stackSize);    
  60.         }   
  61.    
  62.         /** 
  63.           *  启动一个新的线程,如果target  不为空则执行  target  的  run  函数 
  64.           *  否者执行当前对象的run()方法 
  65.           */   
  66.         public  synchronized  void  start()  {   
  67.                 //调用  nativeCreate  启动新线程   
  68.                 nativeCreate(this,  stackSize,  daemon);    
  69.                 started  =  true;    
  70.         }   
  71.    
  72.         @Override   
  73.         public  void  run()  {   
  74.                 if  (target  !=  null)  {   
  75.                         target.run();    
  76.                 }   
  77.         }   
  78. }   

      上面是 Thread 的部分源码,我们看到其实 Thread 也实现了 Runnable 接口,最终被线程执行的是 Runnable,而非 Thread,Thread 只是对 Runnable 的包装,并且通过一些状态对 Thread 进行管理与调度,Runnable 定义了可执行的任务它只有一个无返回值的 run() 函数,如下: 
 
[java]  view plain  copy  
  1. public  interface  Runnable  {   
  2.         /** 
  3.           *  When  an  object  implementing  interface  < code> Runnable< /code>   is  used 
  4.           *  to  create  a  thread,  starting  the  thread  causes  the  object‘s 
  5.           *  < code> run< /code>   method  to  be  called  in  that  separately  executing 
  6.           *  thread. 
  7.           *  < p>  
  8.           *  The  general  contract  of  the  method  < code> run< /code>   is  that  it  may 
  9.           *  take  any  action  whatsoever. 
  10.           * 
  11.           *  @see  java.lang.Thread#run() 
  12.           */   
  13.         public  abstract  void  run();    
  14. }   

当启动一个线程时,如果 Thread 的 Target 不为空时,则会在子线程中执行这个 target 的 run() 函数,否则虚拟机就会执行该线程自身的 run() 函数 
 
二、线程的 wait、sleep、join、yield
 
        Thread 的基本用法相对来说比较简单,通常就是复写 run() 函数,然后调用线程的 start() 方法启动线程,接下来我们来看看 wait、sleep、join、yield 的区别
1)wait
        当一个线程执行到 wait() 方法时,它就进入到一个和该对象相关的等待池中,同时释放对象的机锁,使得其它线程可以访问,用户可以使用 notify、nitifyAll 或者指定睡眠时间来唤醒当前等待池中的线程,注意:wait()、notify()、notifyAll() 必须放在 Synchronized 块中,否则会抛出异常
2)sleep
        该函数是 Thread 的静态函数,作用是使调用线程进入睡眠状态,因为 sleep() 是 Thread 类的静态方法,因此它不能改变对象锁,所以当在一个 Synchronized 块中调用 sleep() 方法时,线程虽然休眠了,但是对象的锁并没有被释放,其它线程无法访问这个对象,即使睡眠也持有对象的锁
3)join
等待目标线程执行完之后再继续执行
4)yield
【Android 多线程编程初探】线程礼让,目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其它线程可以优先执行,但其他线程能否优先执行未知
 
三、线程池
 
        当我们需要频繁的创建多个线程进行耗时操作时,每次都经过 new Thread 实现并不是一种好的方式,每次 new Thread 新建和销毁对象的性能较差,线程缺乏统一管理,可能无限制新建线程,相互之间竞争,可能占用过多系统资源导致死锁,并且缺乏定时执行,定期执行,线程中断等功能,Java 提供了 4 种线程池,它能够有效的管理,调度线程,避免过多的浪费系统资源,它的优点如下:
 
1)重用存在的线程,减少对象创建,销毁的开销
2)可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多的资源竞争,避免堵塞
3)提供定时执行、定期执行、单线程、并发数控制等功能
 
        简单来说,线程池原理简单的解释就是会创建多个线程并进行管理,提交给线程的任务会被线程池指派给其中的线程进行执行,通过线程池的统一调度、管理使得多线程使用更加简单、高效,如下图:
Android 多线程编程初探

文章图片

        线程池都实现了 ExecutorService 接口,改接口定义了线程池需要实现的接口,它的实现有   ThreadPoolExecutor 和 ScheduledThreadPoolExecutor,ThreadPoolExecutor 也就是我们运用最多的线程池实现,ScheduledThreadPoolExecutor 用于周期性执行任务,通常我们都不会直接通过 new 的形式来创建线程池,由于创建参数过程相对复杂一些,因此 JDK 给我们提供了一个 Executor 工厂类来简化这个过程
 
线程池的使用准则:
1)不要对那些同步等待的其他任务结果的任务排队,这可能会导致死锁,在死锁中所有所有线程都被一些任务所占用,这些任务依次等待排队任务的结果,而这些任务又无法执行,因为所有的线程处于忙碌状态
2)理解任务,要有效的调整线程池的大小,你需要理解正在排队的任务以及它们正在做什么
3)调整线程池的大小基本上就是避免两类错误,线程太少或线程太多,幸运的是对于大多数应用程序来说,太多和太少之间的余地相当宽
 
今天介绍的基本都是一些理论的东西,有的看起来可能没劲,大家就当了解一下吧,又到周五了,祝大家周末愉快
 
参考:郭神第一行代码,android 进阶







    推荐阅读