关于Android多线程的理解

扩展文章
非主线程中能不能直接new Handler()
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
JAVA中线程同步的方法
关于AsyncTask、HandlerThread的理解
关于Service,IntentService的理解

扩展知识

  • 线程和进程有什么区别?
    一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

  • 多线程编程的好处是什么?
    多个线程被并发的执行以提高程序的效率,提高CPU和内存利用率

  • 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
    线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

  • 什么是ThreadLocal?

ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
ThreadLocal是数据的隔离,但是并非数据的复制,而是在每一个线程中创建一个新的数据对象,然后每一个线程使用的是不一样的。
每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。

  • 什么是线程池
    一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。
  • 创建线程
    JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
1、继承Thread类实现多线程

继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class MyThread extends Thread {
  public void run() {
   System.out.println("MyThread.run()");
  }
}
在合适的地方启动线程如下:
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();

2、实现Runnable接口方式实现多线程

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,如下:

public class MyThread extends OtherClass implements Runnable {
  public void run() {
   System.out.println("MyThread.run()");
  }
}

为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();

事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() {
  if (target != null) {
   target.run();
  }
}

3、使用ExecutorService、Callable、Future实现有返回结果的多线程。

执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待

  Log.w("TAG", "----程序开始运行----");
                Date date1 = new Date();
                int taskSize = 2;
                // 创建一个线程池
                ExecutorService pool = Executors.newFixedThreadPool(taskSize);
                // 创建多个有返回值的任务
                List<Future> list = new ArrayList<Future>();
                for (int i = 0; i < taskSize; i++) {
                    Callable c = new MyCallable(i + " ");
                    // 执行任务并获取Future对象
                    Future f = pool.submit(c);
                    // System.out.println(">>>" + f.get().toString());
                    list.add(f);
                }

                // 关闭线程池
                pool.shutdown();

                // 获取所有并发任务的运行结果
                for (Future f : list) {
                    // 从Future对象上获取任务的返回值,并输出到控制台
                    try {
                        Log.w("TAG", ">----123-->>" + f.get().toString());
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                Date date2 = new Date();
                Log.w("TAG", "----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
            }

public class MyCallable implements Callable<Object> {
    private String taskNum;

    public MyCallable(String taskNum) {
        this.taskNum = taskNum;
    }

    public Object call() throws Exception {
        Log.w("TAG", ">call---->>" + taskNum + "任务启动");
        Date dateTmp1 = new Date();
        Thread.sleep(3000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        Log.w("TAG", ">>>" + taskNum + "任务终止");
        return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
    }

}

执行结果,两个任务中止后,将结果返回

 W/TAG: ----程序开始运行----
 W/TAG: >call---->>0 任务启动
 W/TAG: >call---->>1 任务启动
 W/TAG: >>>0 任务终止
 W/TAG: >----123-->>0 任务返回运行结果,当前任务时间【3006毫秒】
 W/TAG: >>>1 任务终止
 W/TAG: >----123-->>1 任务返回运行结果,当前任务时间【3005毫秒】
 W/TAG: ----程序结束运行----,程序运行时间【3029毫秒】

关于线程池

ExecutorService与生命周期

ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止 。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。

Java通过Executors提供四种线程池,分别为:

  • CachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。在线程空闲60秒后终止线程。
    ②最大线程数Integer.MAX_VALUE,最多65535个线程
    ③超时时间60s
  • ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • FixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • SingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    ②最大线程数Integer.MAX_VALUE,最多65535个线程
    他就是线程数量为1的FixedThreadPool,如果向SingleThreadExecutor提交多个任务,那么这些任务会排队,每个任务都会在下个任务开始之前就结束,所有任务都用一个线程,并且按照提交的顺序执行。
    ①一个核心线程
   具体的注释可以点击进去参考源码,
    ExecutorService pool1 = Executors.newCachedThreadPool();
    ExecutorService pool2 = Executors.newFixedThreadPool(3);
    ExecutorService pool3 = Executors.newScheduledThreadPool(3);
    ExecutorService pool4 = Executors.newSingleThreadExecutor();

Executors类用于管理Thread对象,简化并发过程,我们可以看到FixedThreadPool的创建过程:

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

显然这四种线程池方法都是返回实现了ExecutorService接口的ThreadPoolExecutor。
Executor是Java中的概念,是一个接口,真正的线程池实现是ThreadPoolExecutor。它提供了一系列的参数来配置不同的线程池。当然我们也可以直接用ThreadPoolExecutor来自定义更灵活的线程池。

 public ThreadPoolExecutor(
             int corePoolSize,//核心线程数
            int maximumPoolSize,//最大线程数
            long keepAliveTime, //非核心线程的超时时间
            TimeUnit unit,//单位
            BlockingQueue<Runnable> workQueue,//任务队列 
            ThreadFactory threadFactory//线程工厂
            RejectedExecutionHandler handler) 
  • corePoolSize
    核心线程数,核心线程池默认会在线程池中一直存活,无论它是不是闲置。如果将ThreadPoolExecute的allowCoreThreadTimeOut设置为true,那么闲置的核心线程会执行超时策略,这个超时策略的时间间隔是由keepAliveTime所指定的。
  • maximumPoolSize
    线程池所能容纳的最大线程数,当活动的线程数达到这个数值后,后续的任务会被阻塞。
  • keepAliveTime
    非核心线程池的超时时长,非核心线程池闲置的时间超过这个时间就会被回收。当ThreadPoolExecute的allowCoreThreadTimeOut设置为true时,它同样也作用于核心线程。
  • unit
    用于指定keepAliveTime的单位。
  • workQueue
    线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会储存在这个参数中。
  • threadFactory
    线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,他只有一个方法:
public interface ThreadFactory {
    Thread newThread(Runnable var1);
}

除了这些参数外还有个很不常用的参数RejectedExecutionHandler handler。当线程池无法执行新任务时(任务队列满了或者无法成功执行)会调用handler的rejectExecutionException。

执行步骤

Android多线程通信机制

  • Android线程间通信方式之AsyncTask机制
  • Handler机制;
  • runOnUiThread方法,用Activity对象的runOnUiThread方法更新,在子线程中通过runOnUiThread()方法更新UI
  • View.post(Runnable r) 、
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容