wedge

Socrates once said, “Learn multithreading, and you learn to squeeze the CPU as capitalists do the proletariat.”

Multithreading is an essential technical point for developers, but also a difficult point for beginners to grasp well. To design a good program, it must be reasonable thread scheduling. Today I will give you a detailed Android and multithreading related knowledge points, unveiled the mysterious veil of multithreading.

This article only introduces the various ways to achieve multithreading, but more in-depth basic principles of exploration, to achieve “what you see is what you learn, what you learn can be used” effect. An in-depth look at the various principles of multithreading will be presented in a later column.

What is multithreading? Why should I use multithreading?

The concept of threads and processes

According to the description of operating system, thread is the smallest unit of CPU scheduling, and thread is a limited system resource. A process generally refers to a unit of execution, and on PC and mobile devices refers to a program or application. A process can contain multiple threads.

To be simple, an Android APP is a process, and there are multiple threads in an APP. The meaning of multi-threaded programming is to achieve “multiple threads in one APP”.

Can I have multiple processes in an APP? Can a process have only one thread?

I’m telling you, you can, you can.

Single-threaded apps with only Android UI threads can also run; Multiple processes in an APP can also be achieved, and the implementation method involves the IPC mechanism of Android, which will not be detailed here.

Why multithreading?

So if you can run with one thread, why do I need multiple threads?

Let me tell you, first of all, this statement is almost a false statement from an Android development perspective. Because Google dad now dictates that you can’t do time-consuming operations in the UI thread, you have to put them in child threads unless your program doesn’t involve time-consuming operations. The reason is that time-consuming operations in the UI thread give the user experience of interface “lag”. Also, an ANR(Application Not Responding) error is fired if the UI thread is blocked for more than a certain amount of time.

At an underlying level, multithreading enables the entire environment to execute asynchronously, which helps improve efficiency by preventing wasted CPU clock cycles. In other words, multithreading can make better use of CPU resources, thus improving the efficiency of the program.

How do I do multithreaded programming?

Thread class and Runnable interface

To define a Thread, simply create a class that inherits Thread and override the run method of its parent

class MyThread extends Thread {
    @Override
    public void run(a) { doSomething(); }}// Start the thread when needed
new MyThread().start();
Copy the code

Optimize?

Instead of inheriting the entire Thread class, we could just implement the Runnable interface

class MyThread implements Runnable {
    @Override
    public void run(a) {
        doSomething()
    }
}

// Start the thread
MyThread myThread = new MyThread();
new Thread(myThread).start();
Copy the code

What if I don’t want to write another thread class? Anonymous classes can be used

new Thread(new Runnable() {
    @Override
    public void run(a) {
        doSomething();
    }
}).start();
Copy the code

The thread pool

Meaning of thread pools

If I can use the Runnable interface to create threads, why do I need a thread pool? In fact, creating threads at random is not recommended in real development. Why? Because threads are also a resource, there is a performance overhead associated with repeated creation and destruction of threads. In contrast, thread pools have the following advantages:

  • Reuse threads in the thread pool to avoid the performance overhead associated with thread creation and destruction
  • It can effectively control the maximum number of concurrent threads in the thread pool and avoid the blocking phenomenon caused by the mutual preemption of system resources between a large number of threads
  • Thread can be simple management, and provides periodic execution and specified interval loop execution

The structure and principles of thread pools

A complete thread pool should have several components

  • Core thread
  • Task queue
  • Non-core thread

When we perform asynchronous tasks through a thread pool, we do the following in sequence

  1. Check whether the number of core threads reaches the maximum value. If not, create a new core thread to execute the task. If yes, go to the next step
  2. Check whether the task queue is full. Otherwise, add the task to the task queue. If yes, go to the next step
  3. Check whether the number of non-core threads reaches the maximum value. Otherwise, a new non-core thread is created to perform tasks. If yes, the thread pool is saturated and the saturation policy is implemented. The default saturation strategy is to throw RejectedExecutionException anomalies

The following hand rub implementation of a thread pool

/ / number of CPU core
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// Number of core threads
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
// Maximum number of threads
private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
// The timeout for non-core threads to be idle
private static final int KEEP_ALIVE_TIME = 1;
// Task queue
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);
/ / thread pool
private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
        MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, sPoolWorkQueue);

private void fun(a){
    Runnable runnable = new Runnable() {
        @Override
        public void run(a) {
            // Child threads handle time-consuming operationsdoSomething(); }}; poolExecutor.execute(runnable); }Copy the code

This allows us to implement a simple thread pool with core threads as number of cpus +1, non-core threads as number of cpus *2+1, non-core threads idle for 1 second, and a task queue size of 128.

There are several specific classifications and implementations of thread pools, which I won’t go into here.

Handler

Some friends may say, you are talking about these are Java multithreading inside things, can we dot our Android unique? OK, now it’s professional time.

Handler is an asynchronous message processing mechanism provided by Android. To learn how to use this mechanism, we should first understand the four brothers of message processing:

  • Message
  • Handler
  • MessageQueue
  • Looper

A Handler can help us pass messages between different threads, where Message is the Message body, which is the thing we want to pass.

Handler plays the role of message Handler, and its main role is to send and process messages.

MessageQueue is a MessageQueue in which messages sent by the Handler are stored. Each thread has only one MessageQueue object.

The Looper is the manager of the MessageQueue in the thread. It runs in an infinite loop, and whenever it finds a message in the MessageQueue, it pulls it out and sends it to the Handler. There can also be only one Looper object per thread.

Now that the basics are understood, let’s backhand rub a Handler

private static final int FLAG = 1;

private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        if (FLAG == msg.what){
            // This is back to the main threaddoSomething(); }}};private void fun(a){
    new Thread(new Runnable() {
        @Override
        public void run(a) {
            // The child thread sends the message
            Message message = new Message();
            message.what = FLAG;
            mHandler.sendMessage(message);
        }
    }).start();
}
Copy the code

AsyncTask

In addition to Handler, Google Dad also provides AsyncTask for thread switching. AsyncTask is a lightweight asynchronous task that performs background tasks in a thread pool and then passes the progress and final results of the execution to the main thread. In terms of implementation principle, AsyncTask is the reencapsulation of Thread and Handle.

AsyncTask itself is an abstract generic class with four children:

  • onPreExecute()
  • doInBackground(Params… params)
  • onProgressUpdate(Progress… values)
  • onPostExecute(Result result)

The first method to execute is onPreExecute(), which is on the main thread and is usually used to do some preparatory work.

The doInBackground() method, located in the thread pool, is then executed to execute the asynchronous task, with params representing the input parameter of the asynchronous task. This method needs to return the result to the onPostExecute() method.

The onProgressUpdate() method is executed in the main thread and is called when the progress of the background task changes.

The onPostExecute() method is called after the last asynchronous task has completed, in the main thread, and the result argument is the return value of the background task, which is the return value of doInBackground().

OK, now that we know the basics, let’s hand rub an AsyncTask

class DownloadTask extends AsyncTask<Void.Integer.Boolean> {

    @Override
    protected void onPreExecute(a) {
        // Here we use a Dialog to show progress, implementation is not table
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            while (true) {// call our doDownload method
                int downloadPercent = doDownload();
                // Use the publishProgress method to update the progress of execution
                publishProgress(downloadPercent);
                if (downloadPercent >= 100)
                    break; }}catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // Update the download progress
        progressDialog.setMessage("Download "+values[0] +"%");
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        // Download completeprogressDialog.dismiss(); }}Copy the code

Here we create a Download class that inherits from AsyncTask and has three generics: void means that the background task does not need to pass in parameters, Integer means that the progress is displayed as an Integer type, and Boolean means that the execution results of the background task are reported as Boolean types.

To make our AsyncTask work, we simply execute:

new DownloadTask().execute();

IntentService

IntentService is a special Service that inherits Service and is an abstract class that can be subclassed to use. IntentService can also be used to perform time-consuming tasks in the background, and it stops automatically when the task is completed.

Intentservices, because they are services, have a higher priority than pure threads and are less likely to be killed by the system.

IntentService (IntentService) encapsulates HandlerThread (IntentService) and Handler (IntentService). The IntentService (IntentService) encapsulates HandlerThread (IntentService) and Handler (IntentService).

RxJava

Some people might say, well, these methods you’re talking about, each one is longer and each one is more complicated, can’t you do something simple and crude?

This time you need to sacrifice a magic weapon RxJava.

What is RxJava?

In fact, the network RxJava entry articles as many as crucian carp, here is not going to be too much in-depth introduction. RxJava is responsive programming, which can be roughly translated into a more elegant multithreaded implementation if you don’t quite understand it.

So how do you work with RxJava?

Start with a common implementation of RxJava

private void fun(a){
    Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
            emitter.onNext(1); }}); observable.subscribeOn(Schedulers.io())// indicates that the subscription is executed in the IO thread
            .observeOn(AndroidSchedulers.mainThread())  // the subscription is received on the main thread
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {
                    // called before receiving a subscription
                }

                @Override
                public void onNext(Integer integer) {
                    // Received subscription successfully called
                    doSomething();
                }

                @Override
                public void onError(Throwable e) {
                    // Receive subscription error call
                }

                @Override
                public void onComplete(a) {
                    // Receive the subscription to complete the call}}); }Copy the code

Emmmmm still looks complicated, can you make it simpler?

OK, chain call plus lambda arrangement

private void fun(a) {
    Observable.create(emitter -> emitter.onNext(1))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(integer -> {
                // Received the subscription successfully
                doSomething();
            }, throwable -> {});
}
Copy the code

HMM… There’s an internal taste.

This string of code we’re sending an Integer;

SubscribeOn () specifies that the thread we send is an IO thread in the background, which can be understood as a child thread;

ObserveOn specifies the thread we receive as the master thread;

Subscribe receives only successful messages, equivalent to the OnNext() method above, essentially we create a Comsumer object here to receive;

Throwable is called when the receive fails, equivalent to the onError() method above.

RxJava has as many as dozens of operators, flexible use can achieve a variety of different asynchronous tasks, here will not spend a lot of space detailed introduction, interested friends can go to viewReactiveX Chinese document

RxKotlin

RxKotlin can be understood as a variant of RxJava on Kotlin, the principle is the same, but the operation language becomes Kotlin, and then encapsulated to make it more elegant call, here to give you a specific implementation case, no more explanation.

private fun test(a) {
    Observable.create<Int> { 1 }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                    onNext = {},
                    onError = {}
            )
}
Copy the code

Kotlin coroutines

Coroutines are not the same thing as threads. What is a coroutine? Coroutines are essentially lightweight threads, according to the official documentation. Since it is lightweight, it means that coroutines should have advantages over threads in terms of resource consumption and performance. So basically all of the asynchronous functionality that we used to do with multiple threads can now be replaced by coroutines.

Coroutines are a brand new thing, so I don’t have to explain them here because OF space, and I’ll write an article about coroutines later.

The resources

1. Java Complete Reference Manual, Eighth Edition

2. First Line of Code, Second Edition

3. Android Advanced Light

4. The Art of Android Development

5. ReactiveX Chinese document