线程、线程中断、线程池、多线程、线程安全、HandlerThread及AsyncTask详解
编程语言
2018-04-19 11:55:08
阅读次数: 2
Thread(线程)
- 线程的创建
- 扩展Thread类:
- 实现Runnable接口并将其实例通过Thread构造传递给Thread
- Callable+FutureTask+Thread: 此方法创建的线程有返回值。具体实现:将
实现Callable接口的实例传递给FutureTask的构造函数,并获得FutureTask的实例,
然后传递给Thread的构造。
- 通过线程池: Executors.new…(newSingleThreadExecutor、
newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool)或者
使用ThreadPoolExecutor()。
- 优缺点:
- 通过实现Runnable、Callable接口创建的线程:
- 优点: 可以继承其他类,可以共享同一个Runnable\Callable对象。
- 缺点: 不方便统一管理,不能复用已有线程。
- Thread常用方法说明:
- start(): 已经start的线程不能再次被start,否则会抛出
IllegalThreadStartException
- sleep(): 不会丢失任何监视器的所有权(不会释放锁)
- interrupt(): 中断,当调用Object.wait();Thread.sleep()、Thread.join()
的时候,interrupt状态会被清除,并抛出InterruptException();
- Thread.sleep()与Object.wait()的区别:
- sleep()是Thread类的方法,wait是Object类的方法。
- sleep()不会释放锁,wait()会释放锁,并且需要调用notify/notifyAll来重
新获取CPU执行时间。
- 多线程
- 核心概念:
- 原子性: 和数据库事物类似,即一个操作要么全部执行完成,要么全部不执行
- 可见性: 当多个线程并发访问共享资源时,一个线程对它的修改,其他线程
应立即可以看到。由于读取时会将变量加载到CPU的高速缓存里,变量修改后,CPU
会立即更新该缓存,并没有立即写入堆内存中,导致其他线程访问该变量时,读取的
依然是旧数据。
- 顺序性: 指程序执行的顺序按照代码的先后顺序执行。只要能遵循happens-
before原则,JVM就能保证其执行的顺序性。
- happens-before原则: todo
- 传递规则:
- 锁定规则:
- volatile变量规则: 。。。
- 线程安全:
- 概念: 一个方法或实例在多线程中使用而不会产生问题。
- 保证线程安全的方法:
- 使用 volatile 关键字,保证变量可见性
- 概述: 当使用volatile修饰某个变量时,它会保证变量的修改立即被
立即更新到内存中,并设置该变量其他的缓存为无效。
- 使用synchronized关键字:
- 概述: synchronized既可以保证原子性,又可以保证可以见性。使用
synchronized修饰时,只有同步方法或同步代码块执行完成时,才能释放锁。
- 非静态同步方法:
执行非静态同步方法时,获取的是该类实例对象(一个类可以有多个实例对象,
每new一次,获取一个实例对象)的内置锁,在多线程中调用同一实例的不同同
步方法时,会产生竞争锁。
- 静态同步方法:
执行静态同步方法时,获取的是该类对象(一个类.class只有一个类对象)的内
置锁,在多线程中用类调用不同的同步方法或者使用类的静态对象调用静态或
非静态方法,都会产生竞争锁。
- 使用java.util.concurrent.locks 包中的锁:
- 使用java.util.concurrent.atomic 包中的原子类,例如 AtomicInteger
- 使用线程安全的集合ConcurrentHashMap:
- 总结:
- volatile和synchronized、lock比较: 后者既能保证原子性,又能
保证可见性,但是前者的开销更小,性能更高。
- lock和synchronized比较: 基本相同,但是使用后者只能等到执行完
才能释放锁,而前者可以在任何时候释放锁。
- 线程的中断
- 概述: 当调用java的interrupt()方法时,线程被标记为中断状态,但是
线程并没有停止运行,当调用stop()(已被废弃)方法时,线程会被强行停止,会造成数
据的不稳定,所以如何优雅的中断一个线程十分重要,一个可行的方案是:轮询线程的中
断状态(isInterrupted()),如果线程被标记为中断,则抛出InterruptException()
异常,并在catch中调用Thread.interrupt()重新中断线程。
//伪代码
try {
if(isInterrupted()){
//to do something
}
} catch (InterruptedException e) {
interrupt();//优雅的线程中断方法
}
- 线程池
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行
执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大
大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,
那么线程池会补充一个新线程。
- newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回
收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加
新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系
统(或者说JVM)能够创建的最大线程大小。
- newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
- 优缺点:
- new Thread的缺点:
- 性能差: 每次需要线程时都重新创建和销毁,已有线程不能重用,导
致性能差。
- 缺乏统一管理: 每次new Thread创建的线程,不能和其他线程统一
管理,相互之间可能会产生竞争锁,导致系统死机或者oom。
- 使用 Executors 的优点:
- 性能更佳: 可以重用已存在的线程,减少线程创建与消亡的开销。
- 统一管理:
- HandlerThread:
- 概述: HandlerThread是Android提供的基于串行的形式利用Handler的
原理的实现的线程。它可以一个接一个的处理消息。
- AsyncTask:
- 执行流程:
- onPreExecute()
task 调用execute()方法以后会调用onPreExecute(),该方法运行的主线程,执
行task任务前的准备工作
- doInBackground()
在onPreExecute()方法执行完以后执行,运行的子线程可以调用
publishProgress();更新UI主线程的进度显示
- onProgressUpdate()
在doInBackground方法中调用publishProgress()之后会回调该方法,运行的
主线程
- onPostExecute()
doInBackground执行完以后将其返回值当做参数传递给该方法,更新UI线程运
行的主线程。
- 实现原理: Handler+ThreadPool
- 有几个线程: 默认的ThreadPoolExecutor希望有2-4个核心线程,最好是
cpu count -1 个核心线程。
- 串行还是并行: AsyncTask由于版本的不同,导致了并发的不同,3.0以后是
串行。
- 缺点:
- 优点:
转载自blog.csdn.net/chenyuan_jhon/article/details/79993577