文档章节

Android 多线程,线程池,并发处理以及ANR

KingBoxing123
 KingBoxing123
发布于 2018/03/21 19:53
字数 2765
阅读 77
收藏 6

1. Android 多线程

    1.1 Android中有哪些多线程的方法

        1) Activity.runOnUiThread(Runnable)

        2) View.post(Runnable) ;View.postDelay(Runnable , long)

        3) Handler

        4) AsyncTask

2. Android线程池

Android线程池hreadPoolExecutor是什么

相当于一个容器,容纳的是Thread或者Runable

为什么要使用ThreadPoolExecutor

1、每一个线程都是需要CUP去分配的,如果总是需要new thread,那么会大量耗费CPU资源,导致应用运行变慢,甚至oom

2、ThreadPoolExecutor可以减少销毁和创建的次数,每个工作线程可以重复利用,可执行多个任务

3、可以根据手机cpu核数来控制最大线程数,保证程序合理运行

ThreadPoolExecutor的构造方法

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
  TimeUnit unit, BlockingQueue<Runnable> workQueue) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  Executors.defaultThreadFactory(), defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
  TimeUnit unit, BlockingQueue<Runnable> workQueue, 
  ThreadFactory threadFactory) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  threadFactory, defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
  TimeUnit unit, BlockingQueue<Runnable> workQueue,
  RejectedExecutionHandler handler) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  Executors.defaultThreadFactory(), handler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
  TimeUnit unit,BlockingQueue<Runnable> workQueue, 
  ThreadFactory threadFactory, RejectedExecutionHandler handler) {
  if (corePoolSize < 0 ||
  maximumPoolSize <= 0 ||
  maximumPoolSize < corePoolSize ||
  keepAliveTime < 0)
  throw new IllegalArgumentException();
  if (workQueue == null || threadFactory == null || handler == null)
  throw new NullPointerException();
  this.corePoolSize = corePoolSize;
  this.maximumPoolSize = maximumPoolSize;
  this.workQueue = workQueue;
  this.keepAliveTime = unit.toNanos(keepAliveTime);
  this.threadFactory = threadFactory;
  this.handler = handler;
}

参数 
public ThreadPoolExecutor( 
int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue workQueue, 
ThreadFactory threadFactory) { 
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
threadFactory, defaultHandler); 
}

corePoolSize: 核心线程池大小,也就是是线程池中的最小线程数;核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出;

maximumPoolSize:最大线程池大小,当活动线程数达到这个值,后续任务会被阻塞

keepAliveTime: 
1、线程池中超过corePoolSize数目的非核心线程最大存活时间;闲置时的超时时长,超过这个值后,闲置线程就会被回收,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。 
2、如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止,这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。

unit: 线程池维护线程所允许的空闲时间的单位

workQueue: 
1、线程池所使用的缓冲队列 
2、执行前用于保持任务的队列,也就是线程池的缓存队列。此队列仅保持由 execute 方法提交的 Runnable 任务

threadFactory:线程工厂,为线程池提供创建新线程的功能,它是一个接口,只有一个方法:Thread newThread(Runnable r)

RejectedExecutionHandler: 
1、当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理 
2、线程池对拒绝任务的处理策略。一般是队列已满或者无法成功执行任务,这时ThreadPoolExecutor会调用handler的rejectedExecution方法来通知调用者

线程池几个参数的理解:

1.  比如去火车站买票, 有10个售票窗口, 但只有5个窗口对外开放. 那么对外开放的5个窗口称为核心线程数corePoolSize,而最大线程数maximumPoolSize是10个窗口.

2. 如果5个窗口都被占用, 那么后来的人就必须在后面排队, 但后来售票厅人越来越多, 已经人满为患, 就类似于线程队列new LinkedBlockingQueue<Runnable>()已满.

3. 这时候火车站站长下令, 把剩下的5个窗口也打开, 也就是目前已经有10个窗口同时运行. 后来又来了一批人

4. 10个窗口也处理不过来了, 而且售票厅人已经满了, 这时候站长就下令封锁入口,不允许其他人再进来, 这就是线程异常处理策略.

5. 而线程存活时间keepAliveTime指的是, 允许售票员休息的最长时间, 以此限制售票员偷懒的行时间。休息一下在处理。

ThreadPoolExecutor的执行过程

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法。

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程

  2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

  3. 当提交任务数超过【maximumPoolSize+阻塞队列大小】时,新提交任务由RejectedExecutionHandler处理 (关于这里,网上90%以上的人说当任务数>=maximumPoolSize时就会被拒绝,我不知道依据在哪里,也不知道代码验证过没,经过我的验证这种说法是不成立的,具体的看下边日志分析)

  4. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程

  5. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

一个下载音乐的例子

模拟音乐播放器,点击下载音乐

package com.bourne.android_common.ThreadDemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import com.bourne.android_common.R;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class ThreadPoolExecutorActivity extends AppCompatActivity {
    private final int CORE_POOL_SIZE = 4;//核心线程数
    private final int MAX_POOL_SIZE = 5;//最大线程数
    private final long KEEP_ALIVE_TIME = 10;//空闲线程超时时间
    private ThreadPoolExecutor executorPool;
    private int songIndex = 0;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_pool_executor);
        // 创建线程池
        // 创建一个核心线程数为4、最大线程数为5的线程池
        executorPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 点击下载
     *
     * @param view
     */
    public void begin(View view) {
        songIndex++;
        try {
            executorPool.execute(new WorkerThread("歌曲" + songIndex));
        } catch (Exception e) {
            Log.e("threadtest", "AbortPolicy...已超出规定的线程数量,不能再增加了....");
        }

        // 所有任务已经执行完毕,我们在监听一下相关数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(20 * 1000);
                } catch (Exception e) {

                }
                Li("monitor after");
            }
        }).start();

    }

    private void Li(String mess) {
        Log.i("threadtest", "monitor " + mess
                + " CorePoolSize:" + executorPool.getCorePoolSize()
                + " PoolSize:" + executorPool.getPoolSize()
                + " MaximumPoolSize:" + executorPool.getMaximumPoolSize()
                + " ActiveCount:" + executorPool.getActiveCount()
                + " TaskCount:" + executorPool.getTaskCount()
        );
    }

    public class WorkerThread implements Runnable {
        private String threadName;

        public WorkerThread(String threadName) {
            this.threadName = threadName;
        }

        @Override
        public synchronized void run() {
            boolean flag = true;
            try {
                while (flag) {
                    String tn = Thread.currentThread().getName();
                    //模拟耗时操作
                    Random random = new Random();
                    long time = (random.nextInt(5) + 1) * 1000;
                    Thread.sleep(time);
                    Log.e("threadtest", "线程\"" + tn + "\"耗时了(" + time / 1000 + "秒)下载了第<" + threadName + ">");
                    //下载完了跳出循环
                    flag = false;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String getThreadName() {
            return threadName;
        }
    }
}

xml布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:layout_weight="1"
        android:onClick="begin"
        android:text="点击下载一首歌曲"/>
</LinearLayout>

效果图: 
界面操作:

 
最终结果: 
这里写图片描述

1、这里设置了4个核心线程,也就是4个下载线程

    private final int CORE_POOL_SIZE = 4;//核心线程数
  •  
     /**
     * 点击下载
     *
     * @param view
     */
    public void begin(View view) {
        songIndex++;
        try {
            executorPool.execute(new WorkerThread("歌曲" + songIndex));
        } catch (Exception e) {
            Log.e("threadtest", "AbortPolicy...已超出规定的线程数量,不能再增加了....");
        }
}

2、耗时操作,下载完毕跳出循环

 public class WorkerThread implements Runnable {
        private String threadName;

        public WorkerThread(String threadName) {
            this.threadName = threadName;
        }

        @Override
        public synchronized void run() {
            boolean flag = true;
            try {
                while (flag) {
                    String tn = Thread.currentThread().getName();
                    //模拟耗时操作
                    Random random = new Random();
                    long time = (random.nextInt(5) + 1) * 1000;
                    Thread.sleep(time);
                    Log.e("threadtest", "线程\"" + tn + "\"耗时了(" + time / 1000 + "秒)下载了第<" + threadName + ">");
                    //下载完了跳出循环
                    flag = false;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String getThreadName() {
            return threadName;
        }
    }

 

4. ANR (Application Not Responding)

 ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现anr,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。

    默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

第一:什么会引发ANR?

     在Android里,应用程序的响应性是由Activity Manager和WindowManager系统服务监视的 。当它监测到以下情况中的一个时,Android就会针对特定的应用程序显示ANR:

1.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)
2.BroadcastReceiver在10秒内没有执行完毕

3.service是20秒

造成以上两点的原因有很多,比如在主线程中做了非常耗时的操作,比如说是下载,io异常等。

   

    潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是Thread.sleep()。替代的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。

 

第二:如何避免ANR?

 

1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)

 

2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)

 

3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

 

dub总结:anr异常也是在程序中自己经常遇到的问题,主要的解决办法自己最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现,比如采用Handler+mesage的方式,或者是有时候需要做一些和网络相互交互的耗时操作就采用asyntask异步任务的方式(它的底层其实Handler+mesage有所区别的是它是线程池)等,在主线程中更新UI。

© 著作权归作者所有

共有 人打赏支持
KingBoxing123
粉丝 5
博文 95
码字总数 48684
作品 0
成都
私信 提问
Android ANR介绍与避免

今天面试遇到一个问题(什么是arn,怎么避免?),当时就傻了。回到家,在网上找到答案,在这里做个记录。 Android开发的网友可能发现ANR的字样,到底Android ANR是什么呢? 其实ANR就是Appli...

gavin_jin
2012/02/21
0
0
金九银十中,看看这31道Android面试题

阅读目录 1.如何对 Android 应用进行性能分析 2.什么情况下会导致内存泄露 3.如何避免 OOM 异常 4.Android 中如何捕获未捕获的异常 5.ANR 是什么?怎样避免和解决 ANR(重要) 6.Android 线程...

codeGoogle
2018/10/30
0
0
分享一份非常强势的Android面试题

马上步入金九银十了,是时候看一些面试题去鹅厂了,接下来我将分享一些面试题,每天总结一点点,希望对大家有所帮助! ListView和RecyclerView区别 参考链接: blog.csdn.net/shu_lance/a… ...

codeGoogle
2018/08/23
0
0
Android面试必会知识点 - ANR详解

最近在公司出差过多,感觉自己快被废了,这时候正好有大公司给了面试机会,于是就去试试,虽然最后Tencent没有要我,但是过程中让我对Android有了更新的认知,把我的对于Android的理解又提升...

吴雨声
2018/07/02
0
0
BATJ等大厂最全经典面试题分享

金九银十,又到了面试求职高峰期,最近有很多网友都在求大厂面试题。正好我之前电脑里面有这方面的整理,于是就发上来分享给大家。 这些题目是网友去百度、蚂蚁金服、小米、乐视、美团、58、...

老道士
2018/09/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

5、redis分布式锁

参考链接:https://www.cnblogs.com/linjiqin/p/8003838.html 一:介绍 实现分布式锁有三种方式:1、数据库乐观锁,2、基于redis,3、基于zookeeper。 redis服务端是单线程操作,完美地避免了...

刘付kin
21分钟前
3
0
OSChina 周日乱弹 —— 我重新说

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享矢野立美的单曲《LOVE Theme from TIGA <M-2>》: 《LOVE Theme from TIGA <M-2>》- 矢野立美 手机党少年们想听歌,请使劲儿戳...

小小编辑
今天
56
5
Java单例模式学习记录

在项目开发中经常能遇见的设计模式就是单例模式了,而实现的方式最常见的有两种:饿汉和饱汉(懒汉)。由于日常接触较多而研究的不够深入,导致面试的时候被询问到后有点没底,这里记录一下学习...

JerryLin123
昨天
10
0
VSCODE 无法调试

VSCODE 无法调试 可以运行 可能的原因: GCC 的参数忘了加 -g

shzwork
昨天
5
0
理解去中心化 稳定币 DAI

随着摩根大通推出JPM Coin 稳定币,可以预见稳定币将成为区块链落地的一大助推器。 坦白来讲,对于一个程序员的我来讲(不懂一点专业经济和金融),理解DAI的机制,真的有一点复杂。耐心看完...

Tiny熊
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部