1.线程
1.线程与进程的区别。
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
2.创建线程的几种方式
-
1.通过 new Thread的方式创建,可复写 Thread#run方法,Thread.start()方式启动。
-
2.实现Runnable接口,通过Thread调用。
-
3.实现 Callable 接口 ,封装 FutureTask 创建线程的 target 对象。
可以获取线程的执行状态,get方法会阻塞线程,及取消线程。
对比:2 和 3 适合多个线程处理同一份资源的情况。
3.Android中异步任务的执行方式
-
new Thread
- 缺点:缺乏统一管理,可能无限创建线程,相互竞争,占用过多系统资源导致死机或oom
-
AsyncTask
- 轻量级异步任务工具,提供执行任务的进度回调给 UI 线程
- 场景:适用于需要知道任务执行的进度,多个任务串行执行
- 缺点:生命周期不同步,容易造成内存泄露,默认情况所有任务串行执行
//1.串行执行,内部使用 SERIAL_EXECUTOR 执行任务 AsyncTask.execute(runnable); AsyncTask.SERIAL_EXECUTOR.execute(runnable); //2.可并发执行的线程池 AsyncTask.THREAD_POOL_EXECUTOR.execute(runnable);
-
HandlerThread
- 适用于主线程需要和工作线程通信,持续性任务,比如轮询等场景,所有任务串行执行
- 缺点:不会向普通线程一样主动销毁,需要手动销毁,否则造成内存泄露
-
IntentSerivice
- 子线程执行任务,任务执行完后自我结束,不需要手动 stopService
-
ThreadPoolExecutor
- 适用于处理大量耗时较短的任务场景
Executors.newCachedThreadPool();//线程可复用的线程池
Executors.newFixedThreadPool();//固定数量的线程池
Executors.newScheduledThreadPool();//可指定定时任务的线程池
Executors.newSingleThreadExecutor();//线程数量为1的线程池
4.线程的优先级
线程的优先级具有继承性,在某个线程中创建的线程会继承次线程的优先级。
- JDK Api 限制了新设置的线程优先级为[1~10],优先级越高,获取 CPU 时间片的概率越高,UI 线程优先级为 5。
- Android Api,可设置线程优先级为[-20 ~ 19],优先级越低,获取 CPU时间片的概率越高,UI为 -10。
5.线程的几种状态
2.线程并发安全
1.什么是线程安全?
线程安全的本质是能够让并发线程有序的运行 (这个有序有可能是先来后到的排队,有可能有人插队,不论怎样,同一时刻只能一个线程有访问同步资源),执行的结果,能够对其他线程可见。
2.线程安全的分类
- synchronized 关键字
- ReentrantLock 锁
- AtomicInteger… 原子类
3.锁的对比
- 锁适合写操作多的场景,先加锁可以保证写操作的时数据的正确性。
- 原子类适合读操作多的场景,不加锁的特点能够使其读操作性能大幅度提升。
3.如何保证线程安全
AtomicInteger 原子包装类,采用 CAS (Compare-And-Swap) 实现无锁的数据更新,自旋的设计能够有效避免线程因阻塞-唤醒带来的系统资源开销
适用场景:多线程计数,原子操作,并发数量小的场景
Volatile 关键字
volatile 修饰的成员变量在每次被线程访问的时候,都强迫从共享内存重新读取该成员变量的值,而且,当成员变量的值发生变化的时,强迫将变化的值重新写入共享内存中。
注意:不能解决非原子操作的安全性。性能不及原子类高。
volatile int count
public void increment(){
//其他线程可见,原子操作
count = 5;
//非原子操作,其它线程不可见
count = count + 1;
count++;
}
synchronized 关键字
锁 Java 对象,锁 Class 对象,锁同步代码块
- 锁方法上,未获取到 对象锁的其他线程不可以访问该方法
synchronized void method(){}
- 锁 Classs 对象,加在 static 方法上相当于给 Class 对象加锁,哪怕是不同的 Java对象实例。也需要排队执行
synchronized static void method(){}
- 锁代码块上,未获得对象锁的线程可以同步执行代码块之外的代码
void method(){
Log.i(TAG,"code out before the synchronized");
synchronized(this){
}
}
synchronized 的优劣势
优势:
- 如果同步方法中出现了异常,Jvm 也能够为我们自动释放锁,从而避免死锁,不需要开发者手动释放锁
劣势:
- 必须要等到获取锁对象的线程执行完成,或者出现异常,才能释放锁。不能中途释放锁,不能中断一个正在试图获取锁的线程。
- 无法获知多个线程竞争锁的时候,获取锁是否成功,不够灵活
- 每个锁只有单一的条件,不能设置超时等
ReentratLock 悲观锁,可重入锁,公平锁,非公平锁
- 基本用法
ReentratLock lock = new ReentratLock();
try{
lock.lock();
....
}finally{
locak.unLock();
}
void lock();//获取不到会阻塞
boolean tryLock();//尝试获取锁,成功返回 true
boolean tryLock(3000,TimeUnit.MILLISECONDS);//在一定时间内不断尝试去获取锁
void lockInterruptibly();//可使用Thread.interrupt()打断阻塞状态,退出竞争,让给其他线程
- 可重入,避免死锁
ReentratLock lock = new ReentratLock();
public void doWork(){
try{
lock.lock();
doWork();//递归调用,使得同一线程多次获取锁
}finally{
locak.unLock();
}
}
-
公平锁 与 非公平锁
- 公平锁:所有进入阻塞的线程排队一次均有机会执行
- 默认非公平锁:允许线程插队,避免每一个线程都进入阻塞,在唤醒,性能高。因为线程可以插队,导致队列中可能会存在线程饿死的情况,一直得不到执行。
-
Condition 条件对象
- 可以使用 awit-singal 指定唤醒一个(组)线程。相比于 wait-notify 要么全部唤醒,要么只能唤醒一个,更加灵活
ReentrantLock lock = new ReentrantLock(); Condition worker1 = lock.newCondition(); Condition worker2 = lock.newCondition(); class Worker1{ .... worker1.await();//进入阻塞,等待唤醒 .... } class Worker2{ .... worker2.await();//进入阻塞,等待唤醒 .... } class Boss{ if(...){ worker1.signal();//指定唤醒线程1 }else{ worker2.signal();//指定唤醒线程2 } }
4.线程池
为什么引入线程池?
- 降低资源消耗 :通过重复利用已创建的线程降低线程创建和销毁造成的开销。
- 提高相应速度: 当任务到达时,就不需要等待线程的创建就可以立即执行。
- 提高线程的可管理性:线程属于稀缺资源,如果无限制的创建,不近会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配,调优和监控
Java中默认的线程池。
JUC包下 Executors 提供的几种线程池