Android多线程

什么是多线程?


线程是cpu调度的最小单位。java中,一个虚拟机实例对应着一个进程,进程中有n个线程,java为每个进程分配一定的内存区域。

  • 方法区
  • java栈
  • 本地方法栈
  • 程序计数器

其中,方法区和堆区是进程内所有线程所共享的,每个线程都有自己的程序计数器,java栈以及本地方法栈。
线程与进程之间的简要关系如上,借用马士宾老师一句最浅显易懂的话来形容线程就是:

线程是程序不同的执行分支。

线程同步问题


上文提到不同线程有各自的专属内存区域,包括java栈、本地方法栈、程序计数器,java栈中存放着一个个的栈帧,线程中执行的方法就对应着一个个栈帧。如果不同的线程同时写一个int,需要先将int加载到自己的java栈中再来写入,相当于不同的线程都在写int的副本,最后int的值为多少,实在无法控制,所以需要引入同步机制,只有获得同步锁对象后,才能写int,这样每个线程加载的int的副本值都是可确定的对象,数据写入才能安全。
正常使用同步锁,理解同步锁是什么,这对于理解线程同步问题有非常大的帮助。

private Object lock = new Object();
public void demo(){
    synchronized (lock) {
        /* 同步锁是lock */
    }
}

public synchronized void demo2(){
    /* 同步锁是类的实例 */
}

public static synchronized void demo3(){
    /* 同步锁是类 */
}

线程状态说明

  • NEW:线程已经创建,但还没有start
  • RUNNABLE:线程处于可运行状态,一切就绪
  • BLOCKED:线程处于阻塞状态,比如正在等待某个锁的释放
  • WAITING:线程处于等待状态,调用wait方法导致
  • TIMED_WAITING:等待特定的时间,一般指调用sleep方法
  • TERMINATED:终止运行

BLOCKED状态,指还未获取到同步锁,仍在等待当中;WAITING是指调用wait方法,释放自己已经得到的同步锁,同时让自己处于等待状态当中;TIMED_WAITING一般是指调用sleep方法,线程睡了一段时间之后就继续运行。这也间接说明了sleep方法与wait方法的不同,sleep只是单纯让线程睡眠一段时间就立即重新执行,与同步毫无关系。

同步时常用方法说明

  • wait:释放同步锁,使线程进入等待状态当中,等待同步锁对象再次调用notify或notifyAll方法唤醒线程,执行线程其余的逻辑
  • notify:释放同步锁,唤醒其它wait的线程,常与wait搭配使用,进行线程间的通信
  • join:等待调用线程结束,再来执行当前线程的内容
  • sleep:单纯地让线程睡眠一段时间,时间一过,线程恢复成执行状态
  • interrupt:顾名思义,打断线程,如果一个线程正被阻塞在wait上或者线程调用了join方法、sleep方法,此时调用interrupt方法,将清除线程的中断状态,并且抛出异常,可接收此异常消息退出线程等。

正确理解volatile


volatile,是一种并不安全的线程同步方式,但在一定程序上也是有效的。相信大家都在大学里上过计算机基础课程,cpu在执行命令时,先将数据从硬盘上加载到内存中,再将数据从内存中读入到寄存器中。cpu其实是在操纵寄存器当中的数据,在一定的时候再寄存器的数据写回到内存中。cpu并不会在操作数据前都重新从内存中加载到寄存器中,所以寄存器中的数据有可能是老旧的数据,这样也会导致程序异常。

public volatile static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(){
        public void run() {
            int i = 0;
            while (!stop) {
                i ++;
            }
            System.out.println("thread stop i = " + i);
        }
    };
    thread.start();
    Thread.sleep(1000);
    stop = true;
    System.out.println("main thread stop = " + stop);
    thread.join();
}

请看上面的代码,如果不用volatile修饰变量stop,那么这个程序将永远无法停止。使用volatile修饰变量,在使用变量的时候将重新从内存中读取,这在一定程度上将能解决线程同步问题。如果是多线程同时写,使用volatile修饰依然无用。

handler原理


hander原理,网上已经有很多的详细解释了,本人就不再详细解释了,不过需要强调的一句是

如果不特别指定hander的Looper对象,那么hander还是在主线程运行,不要在主线程中做特别耗时的事。但hander确实是异步的。

hander不断向消息列队中发送消息,Looper执行死循环,从消息队列中取出消息,将消息交给handler处理执行。

class WorkThread extends Thread{
    public void run() {
        Looper.prepare();
        Handler handler = new Handler(){
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}

一般handler的使用如上,执行Looper.prepare()方法时,将创建消息队列,创建Looper对象,并将Looper对象保存在ThreadLocal中,ThreadLocal类似于map类的键值对容器,它的key很奇特,就是当前的线程。

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

handler将消息发送给与自己相关联的Looper对象,Looper对象在死循环中(执行loop方法)不停处理消息队列中的消息。请注意,Looper对象的loop方法是在与Looper对象关联的线程中执行的,至此,handler与线程关联起来,可以使用handler处理工作线程中的逻辑。

HandlerThread的源码较简单,类似上述Handler使用代码,不再详述。

AsyncTask使用


AsyncTask,是Android工作线程的一种实现,可以AsyncTask中处理复杂逻辑,并在主线程中更新UI,非常地方便。但人们在使用AsyncTask时常常存在着误区

如果存在多个AsyncTask实例,那么这些AsyncTask将同步执行,不会异常执行,必须等待上一个AsyncTask实例执行完毕,才能执行下一个AsyncTask实例。

本人第一次读AsyncTask源码时,非常惊讶,专门为Android定制的工作线程,居然是同步线性执行的。

AsyncTask中定义着两个线程池,一个负责调度,一个负责真正执行。调度线程池中有一个Runnable队列,调度线程池不停地从队列中取出Runnable对象,交给真正执行的线程池执行。
调度线程池:

private static class SerialExecutor implements Executor {
    final ArrayDeque mTasks = new ArrayDeque();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

mTasks是保存Runable对象的队列,在execute方法中,mTasks向自身添加Runable对象,并且将原本的Runable对象进行一定的修改,当前的Runable对象run方法执行完成后再从mTasks对象中取出第二个Runable对象来执行。
mActive 对象,在第一个AsyncTask启动时肯定为空,mTasks从队列中取值,赋值给mActive对象,并将mActive 交给真正的执行线程池THREAD_POOL_EXECUTOR执行。待mActive逻辑执行完毕,再次从mTasks取值执行。实现前文所提的,多个AsyncTask实例,同步线性执行。

执行线程池配置如下:

    public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

可详细查看AsyncTask源码,学习执行线程池的配置,加深线程池的学习及使用。

更多形式的线程使用

  • Callable,接口,可以获取返回值

  • Future, 接口,可查询任务执行的情况,是否完成等

  • FutureTask,类,实现了future接口以及runable接口
    Thread只支持Runable接口。通过工作线程进行某耗时任务,有时需要取得返回值,但Runable接口无返回值。为了解决此问题,引出两个新的接口,Callable和Future。Callable接口包含返回值,而Future接口,可用于查看当前工作线程的执行情况,是否已经执行完成等。
    FutureTask实现了Future和Runable接口,它的构造函数中还包含Callable接口,在它的run方法中,执行Callable接口的实现逻辑,并将结果保存起来,当工作线程线束时可返回结果。

      void innerRun() {
          if (!compareAndSetState(READY, RUNNING))
              return;
    
          runner = Thread.currentThread();
          if (getState() == RUNNING) { // recheck after setting thread
              V result;
              try {
                  result = callable.call();//调用callable的接口实现逻辑
              } catch (Throwable ex) {
                  setException(ex);
                  return;
              }
              set(result);//保存执行结果
          } else {
              releaseShared(0); // cancel
          }
      }
    

FutureTask,是一个可返回任务执行结果的Runable实现类。示例如下:

public class DemoFutureTask {
static class CallableImpl implements Callable{
    int result = 0;
    @Override
    public Integer call() throws Exception {
        while (result < 10) {
            Thread.sleep(1000);
            result ++;
        }
        return result;
    }
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
    CallableImpl callableImpl = new CallableImpl();
    FutureTask task = new FutureTask<>(callableImpl);
    new Thread(task).start();
    System.out.println("workthread has started");
    while (!task.isDone()) {
        System.out.println("task is not done");
        Thread.sleep(1000);
    }
    System.out.println("task is done and result = " + task.get());
}
}

结果为:

    workthread has started
    task is not done
    ......
    task is not done
    task is done and result = 10

你可能感兴趣的:(Android多线程)