一文带你快速掌握Android并发编程核心技术

目录

写在前面

一、了解Android中的线程

1.1、线程的几种创建方式

1.2、线程的优先级

1.3、线程状态及常用方法

1.4、线程间消息通信

二、线程安全

2.1、了解线程安全

2.1.1、什么是线程并发安全

2.1.2、线程安全的几种分类

2.2、如何保证线程安全

2.2.1、原子类

2.2.2、synchronized

2.2.3、ReentrantLock

2.2.4、ReentrantReadWriteLock

三、线程池

3.1、为什么要引入线程池

3.2、Java中的默认线程池

3.2.1、如何构建线程池

3.2.2、Executors提供的几种线程池

3.2.3、流转状态及任务流程

3.3、实战多线程开发框架


写在前面

一文带你快速掌握Android并发编程核心技术_第1张图片

嘿嘿!可能是年前最后一篇了,珍惜吧,骚年!

本文参考资料:转自慕课网Android移动端架构师体系课电子书《线程与线程池开发核心技术》

Android应用在运行的时候必然会存在一个进程,一个进程中至少会存在一个线程,进程在执行的过程中拥有独立的内存空间,而线程运行在进程内,线程也是CPU调度的最基本的单位。在Android世界中,线程可以粗略的分为两大类:1、UI线程;2、工作线程。UI线程用来更新UI控件,工作线程用来处理耗时任务,两者都是线程所以互为竞争关系,都需要等待CPU分配时间片才能真正运行起来,因此如何高效的使用工作线程来辅助提高UI的流畅度是我们需要注意的问题。

一、了解Android中的线程

1.1、线程的几种创建方式

①、new Thread

  • 可以复写Thread#run方法,也可以传递Runnable对象
  • 缺点:缺乏统一管理,如果无限制创建线程,由于各个线程之间相互竞争,会引发占用过多系统资源导致死机或OOM
//方式一:定义类继承自Thread,并重写run方法
class MyThread:Thread(){
    override fun run() {
        super.run()
        println("第一种方式")
    }
}
MyThread().start()

//方式二:传递Runnable
Thread(Runnable {
    println("第二种方式")
}).start()

②、AsyncTask

  • 轻量级的异步任务工具类,提供任务执行的进度回调给UI线程
  • 使用场景:获取任务执行的进度,多个任务串行执行
  • 缺点:生命周期和宿主的生命周期不同步,可能会发生内存泄露,默认情况下所有任务串行执行

方式一:适用场景:需要知道任务执行进度并且更新UI

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //适用于知道任务执行进度更新UI的业务场景
        MyAsyncTask().execute("Test AsyncTask Execute")
//        MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"Test AsyncTask Execute")
    }

    //三个泛型参数分别为:任务入参的类型,执行进度的类型,执行结果的类型
    class MyAsyncTask : AsyncTask() {

        override fun doInBackground(vararg params: String): String {
            for (i in 0..9) { //模拟任务执行进度计算
                publishProgress(i * 10)
            }
            return params[0]
        }

        //获取任务的执行进度
        override fun onProgressUpdate(vararg values: Int?) {
            Log.e("onProgressUpdate:" ,""+values[0])
        }

        //获取任务的执行结果
        override fun onPostExecute(result: String) {
            Log.e("onPostExecute: ", result)
        }
    }
}

执行结果为:

一文带你快速掌握Android并发编程核心技术_第2张图片

方式二:适用场景:无需获取任务进度

//无需获取任务执行进度,以这种方式提交的任务,所有任务串行执行
//如果有一条任务休眠或者执行时间过长,后面所有的任务都将被阻塞
AsyncTask.execute { Log.e("Run", "AsyncTask execute") }

方式三:适用场景:并发任务执行

//并发任务执行
AsyncTask.THREAD_POOL_EXECUTOR.execute { Log.e("Run", "AsyncTask THREAD_POOL_EXECUTOR execute") }

③、HandlerThread

  • 使用场景:适用于主线程需要和工作线程通信,或者持续性任务比如轮询并且所有任务串行执行
  • 缺点:不会像普通线程一样主动销毁资源,会一直运行,可能会导致内存泄露
class MainActivity : AppCompatActivity() {
    companion object{
        val MSG_WHAT = 1
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //创建一个线程名为handler-thread的线程
        val handlerThread = HandlerThread("handler-thread")
        handlerThread.start()
        //将handlerThread跟Hander绑定
        val myHander = MyHander(handlerThread.looper)
        myHander.sendEmptyMessage(MSG_WHAT)
    }

    //等同于Java中的静态内部类
    class MyHander(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                MSG_WHAT -> Log.e("currentThreadName", Thread.currentThread().name)
            }
        }
    }
}

执行之后会打印出当前线程名:

④、IntentService

  • 使用场景:适用于需要跨页面获取任务执行的进度、结果,比如后台上传图片、批量操作数据库等。如果IntentService启动多次,每个耗时操作会以队列形式在IntentService的onHandleIntent方法中依次执行,串行执行方式结束后,IntentService会自动结束,不需要手动停止,这是它跟Service的区别
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //开启服务
        val intent = Intent(this,WorkService::class.java)
        startService(intent)
    }

    //创建IntentService
    class WorkService: IntentService("WorkService") {
        //重写onHandleIntent方法
        override fun onHandleIntent(intent: Intent?) {
            val path = intent?.getStringExtra("download")?:"默认路径"
            //模拟下载任务等...
        }
    }
}

⑤、线程池

  • ThreadPoolExecutor:适用于快速处理大量耗时较短的任务
Executors.newCachedThreadPool() //线程可复用线程池
Executors.newFixedThreadPool() //固定线程数量的线程池
Executors.newScheduledThreadPool() //可指定定时任务的线程池
Executors.newSingleThreadExecutor() //线程数量为1的线程池

1.2、线程的优先级

当我们创建线程并且调用线程的start()方法之后,实际上这个线程并不会立刻得到执行,因为会受到线程优先级的影响,一般情况下,线程的优先级越高,线程获得CPU时间片的概率越大,该线程就能够越早得到执行,进而线程中的任务就能够越早得到执行,注意这里说的仅仅是概率,并不是一定。

  • 线程的优先级具有继承性,在某线程中创建的线程会继承此线程的优先级。比如我们在UI线程中创建了线程,则该线程的优先级是和UI线程的优先级一样,平等的和UI线程抢占CPU时间片资源
val thread = Thread()
thread.start()
Log.e("ui_priority",Thread.currentThread().priority.toString())
Log.e("th_priority",thread.priority.toString())

  • JDK API:限制了新设置的线程的优先级必须为【1-10】,优先级priority的值越高,获取CPU时间片的概率越高,UI线程优先级为5
val thread = Thread()
thread.priority = Thread.MAX_PRIORITY
  • Android API:可以为线程设置更加精细的优先级(-20-19),优先级priority的值越小,获取CPU时间片的概率越高,UI线程优先级为-10
val thread = Thread(Runnable {
    Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO)
}).start()

1.3、线程状态及常用方法

当一个线程调用了它的start()方法并且获取到CPU的时间片之后,它就有权限执行任务了,但是在任务执行的时候,该线程会涉及到多种状态的切换。下面先来看一下线程的几种状态:

一文带你快速掌握Android并发编程核心技术_第3张图片 一文带你快速掌握Android并发编程核心技术_第4张图片

线程的状态切换过程中经常使用到的方法有如下几个:

一文带你快速掌握Android并发编程核心技术_第5张图片

下面对wait/notify机制做一个简单的实战:

class MainActivity : AppCompatActivity() {
    companion object{
        val obj = Object()
        @Volatile
        var hasNotify = false
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val thread1 = Thread(Runnable1())
        val thread2 = Thread(Runnable2())
        thread1.start()
        thread2.start()
    }

    //适用于一个线程需要等待另一个线程的执行结果或者部分结果的多线程同步的场景
    //要保证wait-notify这两个方法的调用顺序
    class Runnable1 : Runnable {

        override fun run() {
            Log.e("run: ", "thread1 start")
            //同步代码块中传入object,则object成为资源对象
            //只有获取到资源对象锁的线程才有机会执行同步锁下面的同步代码块
            synchronized(obj) {
                try {
                    if (!hasNotify) { //确保不会出现假死现象
                        obj.wait(1000)
                    }
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
            Log.e("run: ", "thread1 end")
        }
    }

    class Runnable2 : Runnable {

        override fun run() {
            Log.e("run: ", "thread2 start")
            synchronized(obj) {
                obj.notify()
                hasNotify = true
            }
            Log.e("run: ", "thread2 end")
        }
    }
}

一文带你快速掌握Android并发编程核心技术_第6张图片

接着对join做一个实战:在主线程中创建一个工作线程,然后调用join方法,那么UI线程会等到工作线程执行完毕之后才会继续执行:

//一个线程需要等待另一个线程执行完毕才能继续执行
//join可以理解为向当前线程插入一条任务
val thread = Thread(Runnable {
    Log.e("run: 1", "" + System.currentTimeMillis())
    try {
        Thread.sleep(1000)
    } catch (e: InterruptedException) {
        e.printStackTrace()
    }
    Log.e("run: 2", "" + System.currentTimeMillis())
})
thread.start()
try {
    thread.join() //执行join方法
} catch (e: InterruptedException) {
    e.printStackTrace()
}
Log.e("UI-Thread: ", "run: " + System.currentTimeMillis())

yield是暂停当前线程并且不会释放资源锁,这个平时几乎用不到,我们就不做演示了,sleep比较简单就是休眠线程,需要注意的是如果放在synchronized同步代码块中执行的话它不会释放资源锁,直到任务执行完毕。

1.4、线程间消息通信

关于子线程向主线程发送消息并处理消息的使用相信大家都很熟悉了,这里就不再多说了,我们主要来说说主线程如何向子线程发送消息并处理消息。

class MainActivity : AppCompatActivity() {
    companion object{
        val MSG_WHAT = 1
        val obj = Object()
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val looperThread = LooperThread("Looper-Thread")
        looperThread.start()
        //让looperthread中循环的消息能够被handler处理,这里需要给它绑定Looper对象
        val handler = object : Handler(looperThread.getLooper()!!) {
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                when (msg.what) {
                    MSG_WHAT -> Log.e("handleMessage: ", Thread.currentThread().name)
                }
            }
        }
        handler.sendEmptyMessage(MSG_WHAT)
    }

    //主线程向子线程发送消息,那么子线程必须要具备消息循环及处理能力
    class LooperThread(name: String) : Thread(name) {
        private var looper: Looper? = null

        fun getLooper(): Looper? {
            //调用该方法时Looper对象可能为空,所以让调用方也就是主线程进入等待状态
            synchronized(obj) {
                if (looper == null && isAlive) {
                    try {
                        obj.wait()
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    }
                }
            }
            return looper
        }

        override fun run() {
            //这里需要使用Looper进行消息循环
            Looper.prepare()
            synchronized(obj) {
                //LooperThread线程执行的时候创建Looper对象
                looper = Looper.myLooper()
                obj.notify() //唤起等待方,即主线程
            }
            Looper.loop() //开启消息循环
        }
    }
}

二、线程安全

2.1、了解线程安全

2.1.1、什么是线程并发安全

线程安全的本质是能够让并发线程有序的运行(这里的有序有可能是先来后到排队,有可能有人插队,但无论如何,同一时刻只能一个线程有权访问同步资源),线程执行的结果能够对其他线程可见。

2.1.2、线程安全的几种分类

  • synchronized关键字
  • ReentrantLock锁
  • AtomicInteger...原子类

一文带你快速掌握Android并发编程核心技术_第7张图片

  • synchronized,ReentrantLock-锁
    一文带你快速掌握Android并发编程核心技术_第8张图片
  • 原子类-自旋
    一文带你快速掌握Android并发编程核心技术_第9张图片
  • 锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
  • 原子类适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

2.2、如何保证线程安全

2.2.1、原子类

  • AtomicInterger原子包装类:CAS(Compare-And-Swap)实现无锁数据更新。自旋的设计能够有效避免线程因阻塞-唤醒带来的系统资源开销。适用场景:多线程计数,原子操作,并发数量小的场景。
  • volatile可见性修饰:修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且当成员变量值发生变化时,强迫将变化的值重新写入共享内存,不能解决非原子操作的线程安全性。性能不及原子类高。

代码演示:

//一个用原子类修饰,一个用volatile修饰,多线程情况下自增,然后输出最后的值
public class AtomicDemo {
    public static void main(String[] args) throws InterruptedException {
        final AtomicTask task = new AtomicTask();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    task.incrementVolatile();
                    task.incrementAtomic();
                }
            }
        };
        //开启两个线程,同时修改两个值
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
        System.out.println("原子类的结果:" + task.atomicInteger.get());
        System.out.println("volatile关键字修饰的结果:" + task.volatileValue);
    }

    static class AtomicTask {
        AtomicInteger atomicInteger = new AtomicInteger();
        volatile int volatileValue = 0;

        void incrementAtomic() {
            atomicInteger.getAndIncrement();
        }

        void incrementVolatile() {
            volatileValue++; //非原子操作
//            volatileValue=volatileValue+1;
//            volatileValue = 20000;
        }
    }
}

结果如下:volatile关键字修饰的结果不正确,因为该操作非原子操作

一文带你快速掌握Android并发编程核心技术_第10张图片

2.2.2、synchronized

该关键字的作用场景分为3种:

  • 锁方法。加在方法上,未获取到对象锁的其他线程都不可以访问该方法
synchronized void printThreadName() { }
  • 锁Class对象。加在static 方法上相当于给Class对象加锁,哪怕是不同的java 对象实例,也需要排队执行
static synchronized void printThreadName() { }
  • 锁代码块。未获取到对象锁的其他线程可以执行同步块之外的代码
void printThreadNam() { 
    String name = Thread.currentThread().getName(); 
    System.out.println("线程:" + name + " 准备好了..."); 
    synchronized (this) { } 
}

下面通过代码来看下具体的应用:

首先来看synchronized作用于方法上,并且多个线程在执行时是访问同一个对象testDemo的同步方法,执行结果是正确的,如果访问的是不同的对象,那么结果就是不正确的,会出现多个乘客购买到同一张票的情况。如果在同步方法前面加上static关键字,不管是否访问的同一个对象,结果都是能够保证线程安全的。

public class SynchronizedDemo {
    private static List tickets = new ArrayList<>();

    public static void main(String[] args) {
        for (int i=0;i<5;i++){
            tickets.add("火车票"+(i+1));
        }
        sellTickets();
    }

    private static void sellTickets() {
        final SynchronizedTestDemo testDemo = new SynchronizedTestDemo();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    testDemo.printThreadDemo(); //访问同一个对象的情况
                    //new SynchronizedTestDemo().printThreadDemo(); //访问不同对象的情况
                }
            }).start();
        }
    }

    private static class SynchronizedTestDemo{

        synchronized void printThreadDemo() {
        //static synchronized void printThreadDemo() { //如果加上static同样也是线程安全的
            String name = Thread.currentThread().getName();
            System.out.println("乘客"+name+"准备好了...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("乘客"+name+"买到的票是:"+tickets.remove(0));
        }
    }
}

一文带你快速掌握Android并发编程核心技术_第11张图片

然后我们来修改printThreadDemo()方法,使用synchronized代码块来修饰:

        void printThreadDemo() {
            String name = Thread.currentThread().getName();
            System.out.println("乘客"+name+"准备好了...");
            synchronized (this){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("乘客"+name+"正在买票...");
            }
            System.out.println("乘客"+name+"买到的票是:"+tickets.remove(0));
        }

结果如下:从结果中可以看到,同步代码块之外的代码所有的线程都可以访问,同步代码块内的代码同样的是只有持有对象资源锁的对象才能够访问

一文带你快速掌握Android并发编程核心技术_第12张图片

总结:

  • 如果synchronized作用于方法上,未获取到对象锁的线程,只能排队,不能访问
  • 如果synchronized作用于代码块上,未获取到对象锁的线程,可以访问同步代码块之外的代码
  • 如果synchronized作用于static方法上,就相当于是给Class对象加锁,由于在JVM中只会存在一份class对象,所以此时无论是否为同一个Java对象,去访问同步方法,都只能排队

2.2.3、ReentrantLock

  • 悲观锁:多个线程竞争同一个资源时,只有获取到锁对象的那个线程才有资格去执行
  • 可重入锁:同一个线程可以在不释放锁的前提下重复的获取锁,一定程度上能够避免死锁
  • 公平锁:所有进入阻塞的线程排队依次均有机会执行
  • 非公平锁:默认非公平锁,允许线程插队,避免每一个线程都进入阻塞,再唤醒,性能高。因为线程可以插队,导致队列中可能会存在线程饿死的情况,一直得不到锁,一直得不到执行

首先通过代码来看下它的基本用法:这里还是演示买票功能

public class ReentrantLockDemo {

    public static void main(String[] args) {
        final ReentrantLockTask task = new ReentrantLockTask();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                task.buyTicket();
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
    }

    static class ReentrantLockTask {
        ReentrantLock reentrantLock = new ReentrantLock();

        void buyTicket() {
            String name = Thread.currentThread().getName();
            try {
                reentrantLock.lock();
                System.out.println(name + "准备好了...");
                Thread.sleep(100);
                System.out.println(name + "买好了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

效果跟上一部分是差不多的:

一文带你快速掌握Android并发编程核心技术_第13张图片

接着来看ReentrantLock可以在不释放锁的情况下多次重复的获取锁,直接在上面的代码中简单修改以下:

一文带你快速掌握Android并发编程核心技术_第14张图片

结果如下:可以看到Thread-0直到执行完成下一个线程才能够获取到锁对象

一文带你快速掌握Android并发编程核心技术_第15张图片

然后再来看公平锁和非公平锁,它俩的区别就在于ReentrantLock的构造方法中传入true还是false,true是公平锁,false是非公平锁,同样的还是修改ReentrantLockTask这个类中的代码:

一文带你快速掌握Android并发编程核心技术_第16张图片 一文带你快速掌握Android并发编程核心技术_第17张图片

结果如下所示:

一文带你快速掌握Android并发编程核心技术_第18张图片 一文带你快速掌握Android并发编程核心技术_第19张图片

2.2.4、ReentrantReadWriteLock

  • 共享锁:所有线程均可同时获得,并发量高,比如在线文档查看
  • 排他锁:同一时刻只有一个线程有权修改资源,比如在线文档编辑

下面来举个例子看下:

public class ReentrantReadWriteLockDemo {
    static class ReentrantReadWriteLockTask {
        private final ReentrantReadWriteLock.ReadLock readLock;
        private final ReentrantReadWriteLock.WriteLock writeLock;
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

        ReentrantReadWriteLockTask() {
            readLock = lock.readLock();
            writeLock = lock.writeLock();
        }

        //读操作
        void read() {
            String name = Thread.currentThread().getName();
            try {
                readLock.lock();
                System.out.println("线程" + name + "正在查看文档...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println("线程" + name + "释放了读锁");
            }
        }

        //写操作
        void write() {
            String name = Thread.currentThread().getName();
            try {
                writeLock.lock();
                System.out.println("线程" + name + "正在编辑文档...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
                System.out.println("线程" + name + "释放了写锁");
            }
        }
    }

    public static void main(String[] args) {
        final ReentrantReadWriteLockTask task = new ReentrantReadWriteLockTask();
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    task.read();
                }
            }).start();
        }
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    task.write();
                }
            }).start();
        }
    }
}

结果如下:从结果中不难看出读操作是所有线程同时进行的,写操作是每次只有一个线程

一文带你快速掌握Android并发编程核心技术_第20张图片

三、线程池

3.1、为什么要引入线程池

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

3.2、Java中的默认线程池

3.2.1、如何构建线程池

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, 
    long keepAliveTime, TimeUnit unit,BlockingQueue workQueue, 
    ThreadFactory threadFactory, RejectedExecutionHandler handler)

参数说明情况如下表所示:

一文带你快速掌握Android并发编程核心技术_第21张图片

3.2.2、Executors提供的几种线程池

//单一线程数,同时只有一个线程存活,但线程等待队列无界 
Executors.newSingleThreadExecutor(); 
//线程可复用线程池,核心线程数为0,最大可创建的线程数为Interger.max,线程复用存活时间是60s. 
Executors.newCachedThreadPool(); 
//固定线程数量的线程池 
Executors.newFixedThreadPool(int corePoolSize); 
//可执行定时任务,延迟任务的线程池 
Executors.newScheduledThreadPool(int corePoolSize);

线程池中的重要方法

void execute(Runnable run)//提交任务,交由线程池调度 
void shutdown()//关闭线程池,等待任务执行完成 
void shutdownNow()//关闭线程池,不等待任务执行完成 
int getTaskCount()//返回线程池找中所有任务的数量 
int getCompletedTaskCount()//返回线程池中已执行完成的任务数量 
int getPoolSize()//返回线程池中已创建线程数量 
int getActiveCount()//返回当前正在运行的线程数量

3.2.3、流转状态及任务流程

  • 线程池流转状态

一文带你快速掌握Android并发编程核心技术_第22张图片

  • execute提交任务流程

3.3、实战多线程开发框架

基于以上内容简单封装一个多线程操作的框架:

package com.jarchie.base.executor;

import android.os.Handler
import android.os.Looper
import androidx.annotation.IntRange
import com.jarchie.base.log.JLog
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock

/**
 * 作者: 乔布奇
 * 日期: 2021-01-14 18:50
 * 邮箱: [email protected]
 * 支持按任务的优先级去执行,
 * 支持线程池暂停.恢复(批量文件下载,上传)
 * 异步结果主动回调主线程
 */
object JExecutor {
    private const val TAG: String = "JExecutor"
    private var isPaused: Boolean = false
    private var executor: ThreadPoolExecutor
    private var lock: ReentrantLock = ReentrantLock()
    private var pauseCondition: Condition
    private val mainHandler = Handler(Looper.getMainLooper());

    init {
        pauseCondition = lock.newCondition()

        val cpuCount = Runtime.getRuntime().availableProcessors()
        val corePoolSize = cpuCount + 1
        val maxPoolSize = cpuCount * 2 + 1
        val blockingQueue: PriorityBlockingQueue = PriorityBlockingQueue()
        val keepAliveTime = 30L
        val unit = TimeUnit.SECONDS

        val seq = AtomicLong()
        val threadFactory = ThreadFactory {
            val thread = Thread(it)
            //executor-0
            thread.name = "Jexecutor-" + seq.getAndIncrement()
            return@ThreadFactory thread
        }

        executor = object : ThreadPoolExecutor(
            corePoolSize,
            maxPoolSize,
            keepAliveTime,
            unit,
            blockingQueue as BlockingQueue,
            threadFactory
        ) {
            override fun beforeExecute(t: Thread?, r: Runnable?) {
                if (isPaused) {
                    lock.lock()
                    try {
                        pauseCondition.await()
                    } finally {
                        lock.unlock()
                    }
                }
            }

            override fun afterExecute(r: Runnable?, t: Throwable?) {
                //监控线程池耗时任务,线程创建数量,正在运行的数量
                JLog.e(TAG, "已执行完的任务的优先级是:" + (r as PriorityRunnable).priority)
            }
        }
    }

    @JvmOverloads
    fun execute(@IntRange(from = 0, to = 10) priority: Int = 0, runnable: Runnable) {
        executor.execute(PriorityRunnable(priority, runnable))
    }

    @JvmOverloads
    fun execute(@IntRange(from = 0, to = 10) priority: Int = 0, runnable: Callable<*>) {
        executor.execute(PriorityRunnable(priority, runnable))
    }

    abstract class Callable : Runnable {
        override fun run() {
            mainHandler.post { onPrepare() }
            val t: T = onBackground();
            //移除所有消息.防止需要执行onCompleted了,onPrepare还没被执行,那就不需要执行了
            mainHandler.removeCallbacksAndMessages(null)
            mainHandler.post { onCompleted(t) }
        }

        open fun onPrepare() {
            //加载进度框
        }
        abstract fun onBackground(): T
        abstract fun onCompleted(t: T)
    }

    class PriorityRunnable(val priority: Int, private val runnable: Runnable) : Runnable,
        Comparable {
        override fun compareTo(other: PriorityRunnable): Int {
            return if (this.priority < other.priority) 1 else if (this.priority > other.priority) -1 else 0
        }

        override fun run() {
            runnable.run()
        }

    }

    fun pause() {
        lock.lock()
        try {
            if (isPaused) return
            isPaused = true
        } finally {
            lock.unlock()
        }
        JLog.e(TAG, "JExecutor is paused")
    }

    fun resume() {
        lock.lock()
        try {
            if (!isPaused) return
            isPaused = false
            pauseCondition.signalAll()
        } finally {
            lock.unlock()
        }
        JLog.e(TAG, "JExecutor is resumed")
    }
}

使用(简单测试):

新建一个页面,内部添加两个按钮,第一个按钮测试线程优先级,第二个按钮测试线程执行完毕之后将结果回调到主线程:

一文带你快速掌握Android并发编程核心技术_第23张图片




    

    

然后在Activity中调用线程池框架:

public class HomeActivity extends AppCompatActivity {
    @BindView(R.id.btnTest1)
    private TextView mBtnTest1;
    @BindView(R.id.btnTest2)
    private TextView mBtnTest2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        ViewUtils.inject(this);
    }

    @OnClick({R.id.btnTest1, R.id.btnTest2})
    private void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnTest1:
                for (int i = 0; i < 5; i++) {
                    final int priority = i;
                    JExecutor.INSTANCE.execute(priority, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(1000 - priority * 100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
                break;
            case R.id.btnTest2:
                JExecutor.INSTANCE.execute(new JExecutor.Callable() {

                    @Override
                    public void onCompleted(String s) {
                        Log.e("乔布奇--->", "onCompleted-当前线程:" + Thread.currentThread().getName());
                        Log.e("乔布奇--->", "onCompleted-执行结果:" + s);
                    }

                    @Override
                    public String onBackground() {
                        Log.e("乔布奇--->", "onBackground-当前线程:" + Thread.currentThread().getName());
                        return "异步任务执行";
                    }
                });
                break;
        }
    }
}

观察结果:可以看到5个线程的执行顺序正是按照优先级排序执行的;线程执行完毕之后也能够正常回调到主线程中;

一文带你快速掌握Android并发编程核心技术_第24张图片

OK,到这里今天要说的内容就差不多了,大部分内容在本文开篇给出的参考资料里面都有,大家可以参考着看一下。

话不多说,各位再见!

祝:工作顺利!

你可能感兴趣的:(Android多线程开发,android并发编程,线程池,线程安全,android线程间通信)