- 线程与进程
- 线程的几种创建方式
- 线程的优先级
- 线程的几种状态与常用方法
- 线程间消息通讯
- 线程安全
Android Thread.png
线程与进程
- 一个进程至少一个线程
- 进程可以包含多个线程
- 进程在执行过程中拥有独立的内存空间,而线程运行在进程内
线程的几种创建方式
-
new Thread:可复写Thread#run方法。也可传递Runnable对象,更加灵活。
- 缺点:缺乏统一管理,可能无限制新建线程,相互之间竞争,极可能占用过多系统资源导致死机或oom
// 传递Runnable对象
1.new Thread(new Runnable(){
...
}).start()
//复写Thread#run方法
2.class MyThread extends Thread{
public void run(){
}
}
new MyThread().start()
-
AysncTask:轻量级的异步任务工具类,提供任务执行的进度回调给UI线程
- 场景:需要知晓任务执行的速度,多个任务串行执行
- 缺点:生命周期和宿主的生命周期不同步,有可能发生内存泄漏,默认情况所有任务串行执行
class AsyncTaskTest extends AsyncTask<String,Integer,String> {
private static final String TAG = "AsyncTaskTest";
@Override
protected String doInBackground(String... params) {
for (int i = 0; i < 10; i++) {
publishProgress(i * 10);
}
return params[0];
}
@Override
protected void onPostExecute(String result) {
Log.e(TAG,"result:" + result);
}
@Override
protected void onProgressUpdate(Integer... values) {
Log.e(TAG,"onProgressUpdate:" + values[0].intValue());
}
public static void main(String[] args) {
//1.子类复写方法
AsyncTaskTest asyncTaskTest = new AsyncTaskTest();
asyncTaskTest.execute("execute asyncTaskTest");
//or
asyncTaskTest.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"execute AsyncTaskTest");
//2.使用execute方法,同样串行执行
AsyncTask.execute(new Runnable() {
@Override
public void run() {
.....
}
});
//3.使用内置THREAD_POOL_EXECUTOR线程池,并发执行
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
}
});
}
}
-
HandlerThread:适用于主线程需要和工作线程通信,适用于持续性任务,比如轮训的场景,所有任务串行执行。
- 缺点:不会像普通线程一样主动销毁资源,会一直运行下去,所以可能会造成内存溢出。
HandlerThread thread = new HandlerThread("concurrent-thread");
thread.start();
ThreadHandler handler = new ThreadHandler(thread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
switch ((msg.what)) {
case MSG_WHAT_FLAG_1:
break;
}
}
};
handler.sendEmptyMessage(MSG_WHAT_FLAG_1);
thread.quitSafely();
//定义成静态,防止内存泄漏
static class ThreadHandler extends Handler {
public ThreadHandler(Looper looper) {
super(looper);
}
}
- IntentService:适用于我们的任务需要跨页面读取任务执行的进度,结果。比如后台上传页面,批量操作数据库等,任务执行完成功后,就会自我结束,所以不需要手动stopService,这是它跟Service的区分。
class MyIntentService extends IntentService{
@Override
protected void onHandleIntent(@Nullable Intent intent){
int conmmand = intent.getInt("command")
·····
}
}
//启动IntentService
context.startService(new Intent())
- ThreadPoolExecutor:适用于快速处理大量耗时较短的任务场景
Excutors.newCachedThreadPool();//线程可复用线程池
Excutors.newFixedThreadPool();//固定线程数量的线程池
Excutors.newScheduledThreadPool();//可指定定时任务的线程池
Excutors.newSingleThreadExecutor();//线程数量为1的线程池
线程优先级
public static void main(String[] args){
Tread thread = new Thread();
thread.start();
int ui_proi = Process.getThreadPriority(0);
int th_proi = thread.getPriority();
//输出结果
ui_proi = 5
th_proi = 5
}
- 线程的优先级具有继承性,在某线程中创建的线程会继承此线程的优先级,那么我们在UI线程中创建了线程,则线程优先级是和UI线程优先级一样,平等的和UI抢占CPU时间片资源。
- JDK Api,限制了新设置的线程的优先级必须为[1~10],优先级priority的值越高,获取CPU时间片的概率越高。UI线程优先级为5
java.lang.Thread.setPriority(int newPriority)
- Android Api,可以为线程设置更为精细的优先级(-20~19),优先级priority的值越低,获取CPU时间片的概率越高。UI线程优先级为-10
android.os.Process.setThreadPriority(int newPriority)
线程的几种状态与常用方法
方法名 | 说明 |
---|---|
NEW | 初始状态,线程被新建,还没调用start方法 |
RUNNABLE | 运行状态,把“运行中”和“就绪”统称为运行状态 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,需要其他线程通知唤醒 |
TIME_WAITING | 超时等待状态,表示可以在指定的时间超时后自行返回 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
image-20230723210346165.png
方法名 | 说明 |
---|---|
wait | 进入等待池,释放资源对象锁,可使用notify.notifyAll,或等待超时来唤醒 |
join | 等待目标线程执行完成后再执行此线程 |
yield | 暂停当前正在执行的线程对象,不会释放资源锁,使同优先级或更高优先级的线程有执行的机会 |
sleep | 使调用线程进入休眠状态,但在一个synchronized块中执行sleep,线程虽然会休眠,但不会释放资源资源对象锁 |
线程间消息通讯
- 主线程向子线程发送消息
//自定义一个looper子线程
class LooperThread extends Thread {
private Looper looper;
public Looper getLooper(){
synchronized (this){
if(looper == null && isAlive()){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
return looper;
}
@Override
public void run() {
Looper.prepare();
synchronized (this){
looper = Looper.myLooper();
notifyAll();
}
Looper.loop();
}
}
//主线程向子线程发送消息
LooperThread looperThread = new LooperThread("looper-thread");
looperThread.start();
Handler handler = new Handler(looperThread.getLooper()){
@Override
public void handleMessage(@NonNull Message msg){
super.handleMessage(msg);
Log.e(TAG,"handleMessage:" + msg.what);
Log.e(TAG,"handleMessage:" + Thread.currentThread.getName);
}
};
handler.sendEmptyMessage(MSG_WHAT_1);
- 子线程向主线程发送消息(很熟悉,这里就省略了)
线程安全
什么是线程并发安全
线程安全的本质是能够让并发线程有序的运行(这个有序有可能是先来后到的排队,有可能有人插队,但不管怎么样,同一时刻只能一个线程有权访问同步资源),线程执行的结果,能够对其他线程可见。
线程安全的几种分类
- synchronized关键字
- ReentrantLock锁
-
AtomicInteger...原子类
image.png
-
synchronized,ReentrantLock-锁
image-20230729222013984.png -
原子类-自旋
image-20230729222203332.png - 锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
- 原子类适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
如何保证线程安全
-
AtomicInteger原子包装类
- AtomicInteger原子包装类,CAS(Compare-And-Swap)实现无锁数据更新。自旋的设计能够有效避免线程因阻塞-唤醒带来的系统资源开销。
- 使用场景:多线程计数,原子操作,并发数量小的场景。
//1、构建对象 AtomicInteger atomicInteger = new AtomicInteger(1); //2.调用 Api atomicInteger.getAndIncrement(); atomicInteger.getAndAdd(2); atomicInteger.getAndDecrement(); atomicInteger.getAndAdd(-2);
-
volatile 可见性修饰
volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫变化的值重新写入共享内存。
不能解决非原子操作的线程安全。性能不及原子类高。
volatile int count; public void increment(){ //其他线程可见 count = 5; //非原子操作,其他线程不可见 count = count + 1; count ++;
-
synchronized
锁java对象,锁Class对象,锁代码块
- 锁方法(本质是锁java对象),加在方法上,未获取到对象锁的其他线程都不可以访问该方法。
synchronized void printThreadName(){ }
- 锁Class对象,加在static方法上相当于给Class对象加锁,哪怕是不同的java对象实例,也需要排队执行。
static synchronized void printThreadName(){ }
- 锁代码块,未获取到对象锁的其他线程可以执行同步块之外的代码
void printThreadName(){ String name = Thread.currentThread.getName(); System.out.println("线程:" + name + "准备好了..."); synchronized(this){ } }
synchronized的优势是什么呢?
- 哪怕我们一个同步方法中出现了异常,那么JVM也能够为我们自动释放锁,能主动从而规避死锁,不需要开发者手动释放锁。
劣势是什么呢?
- 必须要等到获取锁对象的线程执行完成,或者出现异常,才能释放掉。不能中途中途释放锁,不能中断一个正在试图获得锁的线程。
- 另外咱们也不知道多线程竞争锁的时候,获取锁成功与否,所以不够灵活。
- 每个锁仅有单一的条件(某个对象),不能设定超时。
-
ReentrantLock悲观锁,可重入锁,公平锁,非公平锁
- 基本用法
ReentrantLock lock = new ReentrantLock(); try{ lock.lock ... }finally{ lock.unLock() }
void lock();//获取不到会阻塞 boolean tryLock();//尝试获取锁,成功返回true boolean tryLock(3000,TimeUnit.MILLISECONDS);//在一定时间内去不断尝试获取锁 void lockInterruptibly();//可使用Thread.interrupt()打断阻塞状态,退出竞争,让给其他线程
- 可重入,避免死锁
ReentrantLock lock = new ReentrantLock(); public void doWork(){ try{ lock.lock(); doWork();//递归调用,使得统一线程多次获得锁 }finally{ lock.unLock(); } }
-
公平锁与非公平锁
- 公平锁,所有进入阻塞的线程排队依次均有机会执行
- 默认非公平锁,允许线程插队,避免每一个线程都进入阻塞,在唤醒,性能高。因为线程可以插队,导致队列中可能会存在线程饿死的情况,一直得不到锁,一直得不到执行。
-
ReentrantLock进阶用法——Condition条件对象
可使用它的await-singnal指定唤醒一个(组)线程。相比于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 } }
-
ReentrantReadWriteLock共享锁,排他锁
- 共享锁,所有线程均可同时获得,并发量高,比如在线文档查看。
- 排他锁,同一时刻只有一个线程有权修改资源,比如在线文档编辑。
ReentrantReadWriteLock reentrantReadWriteLock; ReentrantReadWriteLock.ReadLock readLock; ReentrantReadWriteLock.WriteLock writeLock;
如何正确的使用锁&原子类
-
减少持锁时间
尽管锁在同一时间只能允许一个线程持有,其他想要占用锁的线程都在临界区外等待锁的释放,这个等待的时间我们希望尽可能的短。
public void syncMethod(){ noneLockedCode1();//2s synchronized(this){ needLockedMethed();//2s } noneLockedCode2();//2s }
-
锁分离
读读,读写,写读。只要有写锁进入才需要做同步处理,但是对于大多数应用来说,读的场景要远大于写的场景,因此一旦使用读写锁,在读多写少的场景中,就可以很好的提高系统的性能。
读锁 写锁 读锁 可以访问 不可访问 写锁 不可访问 不可访问 -
锁粗化
多次加锁,释放锁合并成一次
public void doSomethingMethod(){ synchronized(lock){ //do some thing } ... //这里还有一些代码,做其它不需要同步的工作,但能很快执行完毕 ... synchronized(lock){ //do other thing } }
public void doSomethingMethod(){ //进行锁粗化:整合成一次锁请求、释放 synchronized(lock){ //do something //做其它不需要同步的工作,但能很快执行完毕 //do other thing } }