一个Android应用在创建的时候会开启一个线程,我们称为主线程或者UI线程,如果我们想要访问网络或者数据库等耗时操作时,都会开启一个子线程。
线程的状态
New 新创建状态
Runnable 可运行状态
Blocked 阻塞状态
Waiting 等待状态
Timed waiting 超时等待状态
Terminal 终止状态
线程创建后,调用Thread的start方法,开始进入运行状态,当线程执行wait方法后,线程进入等待状态,进入等待状态的线程需要其他线程通知才能返回运行状态。超时等待相当于在等待状态加上了时间限制,如果超过时间限制,则线程返回运行状态。当线程调用同步方法时,如果线程没有获得锁则进入阻塞状态,当阻塞状态的线程获取到锁时则重新回到运行状态。当线程执行完毕或者遇到意外异常终止时,都会进入终止状态。
创建线程
1.继承Thread类,重写run方法
调用start方法并不是立即地执行多线程的代码,而是使线程变为可运行态,什么时候运行多线程代码是由系统决定的。
2.实现Runnable接口,并实现该接口的run方法
3.实现Callable接口,重写call方法
可以带返回值,可以抛出异常
中断
当一个线程调用interrupt方法时,线程的中断标识位将被置位(中断标识位为true),线程会不时地检测这个中断标识位,以判断线程是否应该被中断。通过Thread.currentThread().isInterrupted()来判断线程是否被置位。
同步
重入锁与条件对象
重入锁ReentrantLock就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。
Lock mLock = new ReentrantLock();
mLock.lock();
try{
...
}
finally{
mLock.unlock();
}
这一结构确保任何时刻只有一个线程进入临界区,临界区就是在同一时刻只能有一个任务访问的代码区;
进入临界区时,却发现在某一条件满足之后,它才能执行。这时可以使用一个条件对象来管理那些已经获得了一个锁但是不能做有用工作的线程。
一个锁对象可以有多个相关的条件对象,可以用newCondition方法获得一个条件对象,我们得到条件对象后条用await方法,当前线程就被阻塞了并放弃了锁。
同步方法
Java中的每一个对象都有一个内部锁,如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法;
内部对象锁只有一个相关条件,wait方法将一个线程添加到等待集合中,notifyAll或者notify方法解除等待线程的阻塞状态。
同步代码块
synchronized(obj){
}
volatile
volatile关键字为实例域的同步访问提供了免锁的机制。
Java内存模型定义了线程和主存之间的抽象关系:线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存中存储了线程共享变量的副本。
并发编程中的3个特性:
1.原子性
对基本数据类型变量的读取和赋值操作时原子性操作。
一个语句包含有多个操作时,就不是原子性操作,只有简单地读取和赋值才是原子性操作。
2.可见性
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的;
当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,所以对其他线程时可见的。
3.有序性
让线程顺序执行同步代码。
当一个共享变量被volatile修饰之后,一个线程修改了变量的值时,变量的新值对其他线程时立即可见的。
volatile不保证原子性
eg.自增操作是不具备原子性的,它包括读取变量的原始值,进行加1,写入工作内存。
volatile保证有序性
运用场景:
1.状态标志
2.双重检查模式(DCL)
public class Singleton{
private volatile static Singleton instance = null;
public static Singleton getInstance(){
if(instance==null) {
synchronized(this){
if(instance==null) {
instance = new Singleton();
}
}
}
}
}
//getInstance方法中对Singleton进行了两次判空,第一次是为了不必要的同步,第二次是只有在Singleton等于null的情况下才创建实例。
阻塞队列BlockingQueue
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
阻塞场景:
1.当队列中没有数据的情况,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
2.当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
Java中的阻塞队列:
ArrayBlockingQueue:用数组实现的有界阻塞队列,FIFO,是维护一个Object类型的数组
LinkedBlockingQueue:基于链表的阻塞队列
PriorityBlockingQueue
DelayQueue
SynchronousQueue
LinkedTransferQueue
LinkedBlockingDeque
线程池
通过ThreadPoolExecutor来创建一个线程池
构造方法:
public ThreadPoolExecutor(
int corePoolSize,//核心线程数
int maximumPoolSize,//线程池允许创建的最大线程数,当活动线程数达到这个数值后,后续的新任务会被阻塞
long keepAliveTime,//非核心线程闲置的超时时长,超过这个时长,非核心线程就会被回收,当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用与核心线程
TimeUnit unit,//keepAliveTime参数的时间单位
BlockingQueue workQueue,//线程池中的任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//饱和策略
){}
线程池的处理流程:
1.如果线程池中的线程数量未达到核心线程的数量,那么直接启动一个核心线程来执行任务。
2.如果线程池中的线程数量达到或则超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
3.如果由于任务队列已满,无法将任务插入到任务队列中,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
4.如果线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
线程池种类:
1.FixedThreadPool
通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池;FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列没有大小限制。
2.CachedThreadPool:
它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。
比较合适执行大量的耗时较少的任务。
3.SingleThreadExecutor 单个工作线程的线程池
只有一个核心线程,它确保所有的任务在同一个线程中顺序执行
4.ScheduledThreadPool 能实现定时和周期性任务的线程池