android 多线程
-
线程与进程的区别
一个进程属于一个独立的运行空间,可以理解为一个程序或者一个应用,而线程属于进程中的一个执行任务,线程是进程的一个子集,维度不同,一个进程可以有多个线程,每个线程处理的任务可以都不同,不同的进程有不同的内存空间,而一个进程中的所有线程是共享同一片内存空间的.每个线程又有自己单独的栈内存,用来储存本地数据. -
多线程的有点
多个线程同时处理任务会提高程序的运行效率,多个线程是共享堆内存,因此创建多个线程去处理一批任务比创建多个进程处理任务要好,效率更加高.
-
线程的优先级
每个线程都是有优先级的,一般情况优先级高的线程是有优先执行权,这是依赖系统的调度实现的,我们是可以定义线程的优先级别的 (1-10),但这并不能保证优先级高的线程会优先执行. -
启动线程的run方法与start方法
启动一个线程是用start方法,但是这并不代表该线程立即运行,只是说该线程所处的虚拟处理机处于可运行状态,可以有jvm来调度执行,run方法是线程启动后要回调的方法. -
sleep 方法与wait方法区别
sleep 方法会将线程休眠(暂停),是Thread的静态方法,调用此方法会将线程暂停指定时间,将CPU资源让给其他线程,但是不会释放对象锁,其线程暂停过程是一直持有对象锁的.
wait方法是Object的静态方法,此方法会让该线程释放对象锁,进入对象的等待池,调用notify或者notifyAll方法才能唤醒等待池中的线程,进入等锁池,当该线程拿到锁后就可以进入就绪状态. -
sleep方法与yield方法
sleep方法给其他线程运行机会的时候不会考虑其他线程的优先级,但是yield方法只会给相同级别或更高级别的线程运行机会.
sleep 方法会是线程进入阻塞状态,而yield方法会让线程转入就绪状态
sleep方法会抛出阻断异常,而yield没有任何异常 -
死锁(Deadlock)
死锁是指两个以上的线程永远阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。
分析死锁,我们需要查看Java应用程序的线程转储。我们需要找出那些状态为BLOCKED的线程和他们等待的资源。每个资源都有一个唯一的id,用这个id我们可以找出哪些线程已经拥有了它的对象锁。
避免嵌套锁,只在需要的地方使用锁和避免无限期等待是避免死锁的通常办法 -
Android中使用多线程
- 方法一:继承Thread类,重写父类的run方法,用start方法启动.
- 方法二:实现runnable方法
在子线程处理任务后如何更新主线程UI,Google 规定所有更新UI元素的动作必须要在主线程,但是主线程又不能进行耗时工作,所以对应出来一套异步消息机制.Handler
Message:可以在线程之间传递消息,进行数据交换.
Handler:消息的发送与处理
MessageQueue:存放所有message消息,直到被处理,每一个线程只有有个messagequeue.
Looper:用来管理Messagequeue的队列,Looper.loop()后就会进入无限循环中,将messagequeue中的消息取出来给handler,每个线程只有一个Looper -
AsyncTask
public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); //核心线程数 private static final int CORE_POOL_SIZE = CPU_COUNT + 1; //最大线程数 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; //非核心线程等待时间 private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; //线程队列 128 private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); .... .... }
为了更好的在子线程中更新主线程UI,Android基于异步消息机制封装的一个工具类,其原理是:
两个线程池 + handler
任务队列线程池:任务调度,让多个线程按序排列
执行线程池:真正具体执行任务的线程池
handler:主线程与子线程消息通讯具体api:
execute:在主线程中执行,触发异步任务.
onPreExecute:执行任务前的准备动作,比如初始化进度条,主线程
doInbakground:子线程执行任务,不可以改变UI ,运行在子线程
onProgressUpdate: 更新进度,在主线程
onPostExectue: 子线程结束时候自动调用该方法,表示线程任务处理完毕
onCancelled:异步任务可取消状态 -
Android线程池
传统的使用线程的方法是直接new Thread创建一个子线程,这样会出现一个严重的问题:在众多任务的时候,系统会为每一个任务创建一个线程,任务执行完毕后又会销毁该线程,这样频繁的创建与销毁线程会消耗大量资源,造成系统卡顿,特别是销毁线程的时候会频繁的调用垃圾回收机制,性能低下且耗时.所以用线程池统一管理线程,对线程进行重复使用,在任务完成后并不是立即销毁线程,而是等待另外的任务.几种常见的线程池:
- ThreadPoolExecutor:基本线程池,
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { ... }
corePoolSize: 该线程池中核心线程的数量。
maximumPoolSize:该线程池中最大线程数量。(区别于corePoolSize)
keepAliveTime:从字面上就可以理解,是非核心线程空闲时要等待下一个任务到来的时间,当任务很多,每个任务执行时间很短的情况下调大该值有助于提高线程利用率。注意:当allowCoreThreadTimeOut属性设为true时,该属性也可用于核心线程。
unit:上面时间属性的单位
workQueue:任务队列,后面详述。
threadFactory:线程工厂,可用于设置线程名字等等,一般无须设置该参数。- 可重用固定线程数 FixedThreadPool
创建一个线程数固定的线程池
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不再添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多
-
按需创建 CachedThreadPool
没有核心线程,只有非核心线程,并且每个非核心线程空闲等待的时间为60s,采用SynchronousQueue队列final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器
-
单个核线的 SingleThreadPool
只有一个核心执行线程,当该线程被占用的时候,其他任务需要进入等待队列创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景 -
定时延时执行 ScheduledThreadPool
final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);//参数是核心数创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景
-
队列中有优先级比较的线程池 PriorityThreadPool
final ExecutorService priorityThreadPool = new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,new PriorityBlockingQueue()); 通过线程优先级来执行任务
-
7种常见的阻塞队列
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。 -
线程池常用方法
1.shutDown() 关闭线程池,不影响已经提交的任务2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。
5.beforeExecute() - 任务执行前执行的方法
6.afterExecute() -任务执行结束后执行的方法
7.terminated() -线程池关闭后执行的方法