1, the preface

In Android, because the main thread can not carry out time-consuming operations, so time-consuming operations have to be put into the child thread to do, so multithreaded development is almost unavoidable in practice. This article will summarize the basics of multithreading.

2. Thread status

A thread can be in the following states: 1. New: indicates the state of being created. The thread is created without calling the start method. There is some groundwork to be done before the thread runs. 2. Runnable: Indicates a running state. The start method was called. A runnable thread may or may not be running. 3. Blocked: The virtual drive is Blocked. Indicates that the thread is blocked by a lock and is temporarily inactive. 4. Waiting. The thread is temporarily inactive until it is reactivated by the thread scheduler. 5. Time waiting: Indicates the timeout waiting state. Unlike the wait state, it can return at a specified time. 6. Terminated: indicates that the state is Terminated. Indicates that the current thread is finished.

The conversion relationship between each state is shown in the following figure.

Thread state

3. Thread creation and termination

There are three ways to create threads in Java:

3.1 Inherit Thread class and duplicate run method.

    class MyThread extends Thread {
        @Override
        public void run() {
            Log.d(TAG, "MyThread Thread name:" + Thread.currentThread().getName());
            Log.d(TAG, "MyThread is running");
        }
    }

    private void createThread() {
        MyThread myThread = new MyThread();
        myThread.start();
    }
Copy the code

3.2 Implement the Runnable interface and implement the RUN method

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            Log.d(TAG, "MyRunnable thread name:" + Thread.currentThread().getName());
            Log.d(TAG, "MyRunnable is running");
        }
    }
    
    private void createRunnable() {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
Copy the code

3.3 Implement Callable interface and call method.

    class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            Log.d(TAG, "MyCallable thread name:" + Thread.currentThread().getName());
            Log.d(TAG, "MyCallable is running");
            return "callable return"; } } private void createCallable() throws ExecutionException, InterruptedException { MyCallable myCallable = new MyCallable(); FutureTask<String> futureTask = new FutureTask<>(myCallable); Thread thread = new Thread(futureTask); thread.start(); String result = futureTask.get(); Log.d(TAG, result); }Copy the code

The Runnnable interface is similar to the Runnnable interface, but it differs from the Runnnable interface in two aspects:

  1. RunnnableIn therunMethod cannot throw an exception, whileCallablethecallMethod can.
  2. RunnnableIn therunMethod does not return a value, andCallablethecallMethod can.CallableYou can get oneFutureObject representing the return result of an asynchronous task, calledgetMethod to retrieve the result and block the current process until the result is returned if the asynchronous task has not completed.

Here is an example that Callable uses to simulate the process of buying and cooking. Runnable implementation, then Callable implementation, for comparison.

Private void runnableCook() throws InterruptedException {// buy foodThread foodThread = new Thread(new)Runnable() {
            @Override
            public void runThread.sleep(5000); thread.sleep (5000); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG,"Pork belly bought.");
                food = new String("Pork belly"); }}); Log.d(TAG,"I'm going to start cooking.");
        Log.d(TAG, "I'm going to make braised pork in brown sauce.");
        Log.d(TAG, "I don't have ingredients."); // start the thread log.d (TAG,"Ask Mr. Wang next door to help me go out and buy food."); foodThread.start(); // Log. D (TAG,"Clean the kitchen, prepare the spices, prepare the dishes."); Thread.sleep(3000); Join foodthread.join (); Log.d(TAG,"Let's start cooking.");
        cook(food);
    }
    
    private void cook(String food) {
        if(textutils.isempty (food)) {// If foodThread.join is not called, it will go to log.d (TAG,"You can't cook without ingredients.");
            return;
        }
        Log.d(TAG, "The dish is ready and ready to eat.");
    }
Copy the code

The code is very simple, it is to define a thread to buy food to do the time-consuming task of buying food, and then the main thread prints the cooking log. Because Runnable does not return a value, after finishing the cooking preparation, it needs to call foodthread.join () to wait for the food to buy back, and then do the cook work. If you call the cook method without waiting for the foodThread work to finish, you will find that there are no ingredients to cook the food.

The foodThread.join method is not called
Call the foodThread.join method

Next, use Callable to complete the process.

 private void callableCook() throws InterruptedException, ExecutionException {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5000);
                Log.d(TAG, "Pork belly bought.");
                return "Pork belly"; }}; FutureTask<String> futureTask = new FutureTask<String>(callable); Thread foodThread = new Thread(futureTask); Log.d(TAG,"I'm going to start cooking.");
        Log.d(TAG, "I'm going to make braised pork in brown sauce.");
        Log.d(TAG, "I don't have ingredients.");
        Log.d(TAG, "Tell xiao Wang next door to go out and buy food.");
        foodThread.start();
        Log.d(TAG, "Clean the kitchen, prepare the spices, prepare the dishes.");
        Thread.sleep(3000);
        if(! futureTask.isDone()) { Log.d(TAG,"The ingredients haven't arrived yet. Wang is so slow.");
        }
        food = futureTask.get();
        Log.d(TAG, "Let's start cooking.");
        cook(food);
    }
    
       private void cook(String food) {
        if(textutils.isempty (food)) {// If foodThread.join is not called, it will go to log.d (TAG,"You can't cook without ingredients.");
            return;
        }
        Log.d(TAG, "The dish is ready and ready to eat.");
    }
Copy the code

The Callable interface is used in a similar way to Runnable.

The first step is to implement the Callable interface and specify a generic type for it. This generic type is the type to be returned by the Call method.

The second step is to create a FutrueTask, also set the return type generic, and pass in the newly implemented Callable object. This FutrueTask is then used to get the results of the asynchronous task.

The last step, as with Runnable, is to create a Thread object, pass in the FutrueTask, and start the Thread.

Unlike Runnable, FutrueTask provides an isDone method to determine whether an asynchronous task is complete, and provides a futureTask.get method to get the result of an asynchronous task. If the task is not finished at this point, it will block the current thread until the task is complete. So you don’t have to start cooking before you’ve bought the ingredients.

Callable implementation

3.4 Terminating a Thread

A thread terminates when its run method finishes running or when an exception occurs in the method. In addition, the Thread class provides stop and interrupt methods to terminate threads. Stop is obsolete and deprecated, while interrupt is invoked and the Thread marks its termination as true.

Safe way to stop a thread: Use a Boolean variable
  private volatile boolean flag = true;

  private void createFlagThread() {
        flag = true;
        count = 0;
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (flag) {
                    Log.d(TAG, "flag count:"+ count++); }}}); thread.start(); } private voidchangeFlag() {
        flag = false;
    }
Copy the code

Call createFlagThread to create a thread. Flag is used in the run method to determine whether to terminate the loop and thus terminate the thread. When the thread needs to be stopped, call changeFlag and set the Boolean value to false.

3.5 Volatile Keyword

In the 3.4 example, a volatile keyword was added to the Boolean variable used to stop the loop. Volatile is known as lightweight synchronized, but volatile only modifiers variables and not methods. You can use the volatile keyword when a variable needs to be accessed by multiple threads. For example, the volatile modifier is used in the classic double-checked singleton pattern.

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if(singleton == null) { singleton = new Singleton(); }}}returnsingleton; }}Copy the code

So is volatile really a substitute for synchronized? The answer is definitely no. The role of volatile starts with three features of concurrent programming and Java’s memory model.

3.5.1 Atomicity, visibility and order
  • Atomicity: Atomicity is an operation that cannot be split. An operation either completes or does not.
Int I = 1; Int j = I; // It is not atomic, because it reads the value of I and then assigns it to jCopy the code
  • Visibility: Visibility refers to the visibility between multiple threads. Changes made by one thread to a state are visible to another thread, meaning the changes are immediately visible to the other thread.
  • Order:Orderliness meansJavaIn the memory model, the compiler and processor are allowed to reorder instructions. Reordering does not affect the correctness of single-threaded execution, but in the case of multiple threads, it will represent the correct line of multithreaded concurrency.
3.5.2 Java Memory model

The Java runtime data area is shown below.

As can be seen from the figure, it is mainly divided into the following parts:

1. Program counter

The program counter is a small memory space. Can be seen as a line number indicator of the bytecode executed by the current thread, thread private.

2. Java VM stack

Thread private. The life cycle is the same as that of a thread. The Java virtual machine stack describes an in-memory model for the execution of Java methods. The process of each method from invocation to completion corresponds to the process of a stack frame being pushed into and out of the virtual machine stack.

3. Local method stack

The function of the vm stack is similar to that of the VM stack, but the difference is that the VM stack executes Java methods (i.e. bytecode services) for the VM, while the local method stack serves the Native methods used by the VM.

4. The Java heap

An area of memory shared by all threads. Created when VMS are started to store object instances. Is the primary area managed by the garbage collector.

5. Methods area

An area of memory shared by threads. Data used to store virtual machine-loaded class information, constants, static variables, and just-in-time compiler compiled code.

6. Runtime constant pool

Is part of the methods area. Used to hold various literal and symbolic references generated at compile time. It’s dynamic.

7. Direct memory

It is not part of the run-time data area of the virtual machine, nor is it an area of memory defined in the Java Virtual Machine specification, but it is heavily used.

As you can see from the above, Java heap memory is an area of memory shared by all threads, so visibility is a problem. The Java memory model defines an abstract relationship between threads and main memory: shared variables between threads are stored in main memory, each thread has its own local memory, and a copy of the shared variables is stored in local memory (local memory is an abstract concept in the Java memory model and does not exist). The abstraction is shown below.

As shown in the figure above, thread A needs to go through two steps to communicate with thread B: First, thread A refreshes the updated shared variables in thread A’s local memory to the main memory; Second, thread B will go to the main thread and read thread A to update the past shared variables.

Returning to the volatile keyword, volatile variables only guarantee two of the three properties of concurrency: visibility and order. It doesn’t guarantee atomicity. Thus, both singletons and thread aborts used volatile to ensure that variables accessed by multiple threads were visible to all threads after modification by one thread. Because atomicity cannot be guaranteed, simultaneous non-atomic operations such as i++ by multiple threads can be problematic even if volatile variables are declared, requiring methods such as synchronized.

4. Thread synchronization

Multithreading accessing the same resource will inevitably lead to thread synchronization problem. If the thread synchronization problem is not solved, resource data errors will be caused. The thread synchronization problem is explained through the classic multi-window ticket problem.

4.1 Multi-window ticket sales

M ticket Windows sell n tickets at the same time, using multithreading implementation. Here’s an example of an error in not synchronizing. Here draws a simple interface, two input boxes input the total number of votes and the number of Windows, click the button to start selling tickets, the final result is displayed by TextView.

<? xml version="1.0" encoding="utf-8"? > <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".TicketActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <EditText
            android:id="@+id/et_ticket_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Enter the total number of votes"
            android:inputType="number"
            android:text="100" />
        <EditText
            android:id="@+id/et_window_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Enter the number of Windows for selling tickets."
            android:inputType="number"
            android:text="3" />
        <Button
            android:id="@+id/btn_begin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="On sale" />

        <TextView
            android:id="@+id/tv_result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="16sp" />
    </LinearLayout>
</ScrollView>
Copy the code

Ticket method code:

Private StringBuilder result = new StringBuilder(); Private int allTicketCount = 0; Private int windowCount = 0; private voidbegin() { allTicketCount = Integer.parseInt(mEtTicketCount.getText().toString().trim()); windowCount = Integer.parseInt(mEtWindowCount.getText().toString().trim()); Result.delete (0, result.length()); result.append("Total votes:" + allTicketCount + "In common" + windowCount + "Sell tickets at every window \n"); mTvResult.setText(result.toString()); // Loop to create multiple window threads and startfor (int count = 1; count <= windowCount; count++) {
            Thread window = new Thread(new TicketErrorRunnable(), "Ticket window"+ count); window.start(); / / TicketErrorRunnable implements Runnable {@override public voidrun() {
            while(allTicketCount > 0) {// If the total number of votes is greater than 0, subtract one from the total number of votes. Result.append (thread.currentThread ().getName() +": Sold one ticket, still to go." + allTicketCount + "Tickets. \n"); // Refresh UI runOnUiThread(newRunnable() {
                        @Override
                        public void run() { mTvResult.setText(result.toString()); }}); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }} UI result.append(thread.currentThread ().getName() +"The tickets are sold out. \n");
            runOnUiThread(new Runnable() {
                @Override
                public void run() { mTvResult.setText(result.toString()); }}); }}Copy the code

Running results:

Do not make synchronization

begin

4.2 Synchronizing code blocks

Putting the code that needs to be synchronized in a synchronized code block keeps the threads synchronized.

*/ class TicketSyncRunnable implements Runnable {@override public void implements Runnablerun() {
            while(allTicketCount > 0) {/ / -- -- -- -- -- -- -- -- -- -- synchronized code block -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - synchronized (TicketActivity. Class) {allTicketCount -; result.append(Thread.currentThread().getName() +": Sold one ticket, still to go." + allTicketCount + "Tickets. \n");
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() { mTvResult.setText(result.toString()); }}); } / / -- -- -- -- -- -- -- -- -- -- synchronized code block -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - try {thread.sleep (200); } catch (InterruptedException e) { e.printStackTrace(); } } result.append(Thread.currentThread().getName() +"The tickets are sold out. \n");
            runOnUiThread(new Runnable() {
                @Override
                public void run() { mTvResult.setText(result.toString()); }}); }}Copy the code

Nothing else has changed here, except that the Runnable has been modified to synchronize the count to the synchronization block so that only one thread can operate on allTicketCount at the same time.

Running results:

Synchronized code block

4.3 Synchronization Method

The code that needs to be synchronized is extracted from a method, and the synchronized keyword is added to the method as a synchronized method, which can also ensure the synchronization of threads.

/ / class TicketSyncMethodRunnable implements Runnable {@override public voidrun() {
            ticket();
            result.append(Thread.currentThread().getName() + "The tickets are sold out. \n");
            runOnUiThread(new Runnable() {
                @Override
                public void run() { mTvResult.setText(result.toString()); }}); } // private synchronized voidticket() {
            while (allTicketCount > 0) {
                allTicketCount--;
                result.append(Thread.currentThread().getName() + ": Sold one ticket, still to go." + allTicketCount + "Tickets. \n");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() { mTvResult.setText(result.toString()); }}); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

Pull the action on total votes into the synchronous method to see the result:

Synchronized methods

4.4 ReentrantLock ReentrantLock

Locking your own code also ensures synchronization.

private ReentrantLock reentrantLock = new ReentrantLock(); /** ** / class TicketLockRunnable implements Runnable {@override public voidrun() {
            while(allTicketCount > 0) {// ReentrantLock. lock(); try { allTicketCount--; result.append(Thread.currentThread().getName() +": Sold one ticket, still to go." + allTicketCount + "Tickets. \n");
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() { mTvResult.setText(result.toString()); }}); } finally {// reentrantLock.unlock(); } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } result.append(Thread.currentThread().getName() +"The tickets are sold out. \n");
            runOnUiThread(new Runnable() {
                @Override
                public void run() { mTvResult.setText(result.toString()); }}); }}Copy the code

Running results:

lock

5. Several ways to achieve multithreading in Android

New Thread().start() can be used to create asynchronous tasks, but it is not easy to use new Thread(). There are also unexpected gains (memory leaks).

5.1 AsyncTask

AsyncTask is a class provided by Android that performs asynchronous tasks. Using AsyncTask: First define a class that inherits AsyncTask:

 class MyAsyncTask extends AsyncTask<String, Integer, String> {
        int count = 0;
        @Override
        protected void onPreExecute() {
            //doLog.d(TAG, thread.currentThread ().getName() +"Asynchronous task ready to start" + System.currentTimeMillis());
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            //doLog.d(TAG, thread.currentThread ().getName() +"Mission in progress:" + values[0] + "%");

        }
        @Override
        protected void onPostExecute(String result) {
            //doLog.d(TAG, thread.currentThread ().getName() +"" + result + "" + System.currentTimeMillis());
        }
        @Override
        protected String doInBackground(String... strings) {
            Log.d(TAG, Thread.currentThread().getName() + "Asynchronous task in progress" + System.currentTimeMillis());
            while (count < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count += 10;
                publishProgress(count);
            }
            Log.d(TAG, Thread.currentThread().getName() + "Asynchronous task complete!" + System.currentTimeMillis());
            return Thread.currentThread().getName() + ": Mission completed"; }}Copy the code

The three AsyncTask generic parameters Params, Progress, and Result correspond to the type of the parameter passed in, the type of the intermediate Progress, and the type of the returned Result. AsyncTask has four main callback method replays.

  • DoInBackground: Must be replicated, executed asynchronously on background threads, where time-consuming tasks to be completed are processed and run on child threads.
  • OnPreExecute: performdoInBackgroundIs called to initialize or prepare for time-consuming operations and runs on the main thread.
  • OnProgressUpdate:doInBackgroundCall after execution, return processing results, updateUIEtc., run on the main thread.
  • OnProgressUpdate:doInBackgroundMethod callpublishProgressMethod after the call to update the time-consuming task progress, running in the main thread.

Call the AsyncTask:

   MyAsyncTask myAsyncTask = new MyAsyncTask();
   myAsyncTask.execute();
Copy the code

Running result log:

For further understanding of AsyncTask source code operation principle can see Android advanced knowledge: AsyncTask related

5.2 HandlerThread

A HandlerThread encapsulates a Handler with a Thread, which is essentially a Thread. HandlerThread is used as follows:

HandlerThread = new HandlerThread("myHandlerThread"); // Call the start method to start the thread handlerThread.start(); Handler = new Handler(handlerThread.getLooper()) {Override the handleMessage method to handle the message  public void handleMessage(Message msg) { switch (msg.what) {case 1:
                        Log.d(TAG, Thread.currentThread().getName() + " receive one message");
                        break; }}}; Message = message.obtain (); message.what = 1; handler.sendMessage(message); // Exit HandlerThread handlerthread.quit ();Copy the code

Running result log:

HandlerThread
Android advanced knowledge: HandlerThread related

5.3 IntentService

IntentService encapsulates a Service with a HandlerThread. Unlike a normal Service, which runs on the main thread, IntentService creates a working child thread to execute the task. In addition, IntentService automatically closes the Service after completing the task, whereas ordinary services need to manually call the stopService method. IntentService uses the following:

Public class MyIntentService extends IntentService {private static Final String TAG ="MyIntentService";

    public MyIntentService() {
        super("MyIntentService"); } @override protected void onHandleIntent(Intent Intent) {// Override onHandleIntent implements the task String according to the Intent's delivery parameterstype = intent.getStringExtra("type");
        switch (type) {case "type1":
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG,Thread.currentThread().getName()+" type1 doing");
                break; }}} // AndroidManifest <? xml version="1.0" encoding="utf-8"? > <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.thread.threaddemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
          ......
        <service android:name=".MyIntentService"Intent = new Intent(this, myintentservice.class); /> </application> </manifest> intent.putExtra("type"."type1");
startService(intent);
Copy the code

Run log result:

IntentService
Android advanced knowledge: IntentService related

5.4 Handler

Android developers will be familiar with the Handler, which is a method of communication between multiple threads provided by Android. Android can’t avoid handlers for multithreading. Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler

6. Thread pools

Thread pool is used to manage thread tools, in the development of asynchronous tasks each time to manually create a thread, not only waste resources and not easy to manage control, the use of thread pool can be a good solution to these problems.

6.1 Blocking Queue

Before we look at thread pools, let’s look at blocking queues. Understanding blocking queues will help us learn how thread pools work. Blocking queue is “producers – consumers” in concurrent programming model used to store elements in the container, the producer production elements in a blocking queue, consumers from the blocking queue elements, there is no element or element in the queue when the queue is full blocking happens, until the queue again add or remove elements. Blocking queues enable lock operations when elements are added or removed, so there is no need to consider thread synchronization separately.

There are seven blocking queues available in Java:

  • ArrayBlockingQueue: a bounded blocking queue composed of array structures.
  • LinkedBlockingQueue: a bounded blocking queue consisting of a linked list structure.
  • PriorityBlockingQueue: An unbounded blocking queue that supports priority sorting.
  • DelayQueue: An unbounded blocking queue implemented using priority queues.
  • SynchronousQueue: A blocking queue that does not store elements.
  • LinkedTransferQueue: An unbounded blocking queue layered with a linked list structure.
  • LinkedBlockingDeque: A two-way blocking queue consisting of a linked list structure.

The following is an example of a producer-consumer pattern implemented by using blocking queues.

package com.thread.threaddemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.concurrent.ArrayBlockingQueue;

public class BlockingQueueActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "BlockingQueueActivity"; private Button mBtnMain; private Button mBtnProduce; private Button mBtnConsumer; Private ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(5); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_blocking_queue);
        initView();
    }
    private void initView() {
        mBtnMain = (Button) findViewById(R.id.btn_main);
        mBtnProduce = (Button) findViewById(R.id.btn_produce);
        mBtnConsumer = (Button) findViewById(R.id.btn_consumer);
        mBtnMain.setOnClickListener(this);
        mBtnProduce.setOnClickListener(this);
        mBtnConsumer.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_main:
                mainStart();
                break;
            case R.id.btn_produce:
                addData();
                break;
            case R.id.btn_consumer:
                removeData();
                break; }} /* * Manually remove an element from the blocking queue */ private voidremoveData() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    queue.take();
                    printData(); } catch (InterruptedException e) { e.printStackTrace(); }}},"remove"); thread.start(); } /* * Manually add an element from the blocking queue */ private voidaddData() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    queue.put("data");
                    printData(); } catch (InterruptedException e) { e.printStackTrace(); }}},"add"); thread.start(); } /* * Prints logs */ private voidprintData() {
        Log.d(TAG, Thread.currentThread().getName() + ":"+ queue.toString()); } /* * open the producer consumer thread */ private voidmainStart() {
        ProducerThread producerThread = new ProducerThread("Producer");
        producerThread.start();
        ConsumerThread consumerThread = new ConsumerThread("Consumer"); consumerThread.start(); } /* * class ConsumerThread extends Thread {public ConsumerThread(String name) {super(name); } @Override public voidrun() {
            while (true) {
                try {
                    Thread.sleep(2000);
                    queue.take();
                    printData(); } catch (InterruptedException e) { e.printStackTrace(); }}}} /* * class ProducerThread extends Thread {public ProducerThread(String name) {super(name); } @Override public voidrun() {
            while (true) {
                try {
                    Thread.sleep(1000);
                    queue.put("data");
                    printData();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code

Run print log:

Create a block queue of 5, click the button to call mainStart, and start the producer thread and the consumer thread. The producer thread produces an element every one second to join the queue, and the consumer consumes an element every two seconds to remove the queue. As you can see, since production is faster than consumption, the container is gradually filled up until the queue is full of five elements until the consumer thread consumes the element. Of course, you can also manually add and remove elements from the queue by calling addData or removeData. Again, blocking occurs when the queue is full or empty until a new element is added to the queue.

6.2 the thread pool

6.2.1 Constructor

The core Java thread pool implementation class is ThreadPoolExecutor. Both Runnable and Callable can be managed by a thread pool. To use a thread pool, create a thread pool object.

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());
Copy the code

This creates a thread pool object, which can then call the Execute or Submit submission task, and the thread pool allocates threads to handle the task. But let’s look at the constructor of ThreadPoolExecutor.

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
Copy the code

The constructor is an initial assignment to each of these member arguments, to see what they mean.

  • CorePoolSize:Number of core threads. By default, the thread pool is empty, with fewer threads currently running when a task is submittedcorePoolSizeNumber, a new thread is created to process the task.
  • MaximumPoolSize:Maximum number of threads allowed to be created in a thread pool. If the task queue is full and the number of threads is less thanmaximumPoolSizeThe thread pool still creates new threads to process the task.
  • KeepAliveTime: timeout period for non-core threads to be idle. Any longer than that will be recycled.
  • TimeUnit:keepAliveTimeA unit of time.
  • WorkQueue: indicates a task queue. Is a blocking queue to which tasks are added if the current number of threads is large enough to have core threads.
  • ThreadFactory: ThreadFactory.
  • RejectedExecutionHandler: saturation policy. The policy taken when both the task queue and the thread pool are full.
6.2.2 Execution Process

The thread execution process source code starts with the Execute method of ThreadPoolExecutor.

public void execute(Runnable command) {
        if (command== null) throw new NullPointerException(); int c = ctl.get(); // Check whether the number of worker threads is smaller than the number of core threadsif(workerCountOf(c) < corePoolSize) {// Call addWorker to create the core threadtrueRepresents the core threadif (addWorker(command.true))
                return; c = ctl.get(); } // Otherwise, call the workqueue.offer method to put the task on the task queueif (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false); } // if the queue fails, call the addWorker method to create a non-core thread to execute the taskfalseRepresents a non-core threadelse if(! addWorker(command.false) // If addWorker returnsfalseMaximumPoolSize reject(reject)command);
    }
Copy the code

According to the above code and comments, the specific execution process is as follows:

  1. Determines whether the number of core threads in the thread pool has reached the set maximum number of core threadscorePoolSizeIf the number of core threads is not reached, the core thread is created to process the task.
  2. If the number of core threads has reached, then judge whether the task queue is full, if not, add the task to the task queue.
  3. If the task queue is full, determine whether the maximum number of threads in the thread pool has been reachedmaxmumPoolSizeIf the maximum number of threads is not reached, a non-core thread is created to process the task.
  4. If the maximum number of threads is reached, a saturation policy is implemented.

Here’s a look at reject again:

  final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }
Copy the code

The rejectedExecution method in the saturation policy is invoked. There are four saturation strategies, which are:

  • AbordPolicy:The default is this policy, indicating that the new task cannot be processed and thrownRejectedExecutionExceptionThe exception.
  • CallerRunsPolicy: Process the task with the caller’s thread.
  • DiscardPolicy: indicates that the task cannot be executed and is deleted.
  • DiscardOldestPolicy: Discards the latest task in the queue and executes the current task.

Here is the implementation of the default AbordPolicy.

public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from "+ e.toString()); }}Copy the code

Can see that the implementation is simple is realized RejectedExecutionHandler interface, autotype rejectedExecution method, throw RejectedExecutionException anomalies. The implementation of other saturation strategies is similar.

Thread pools execute processes

6.2.3 Common Thread pools

Several common thread pools are provided by default in Java.

  • FixedThreadPool: a reusable thread pool with a fixed number of threads.
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
Copy the code

As you can see from the creation method, the FixedThreadPool thread pool has the same number of core threads as the maximum number of threads, so it has no non-core threads. And its thread idle timeout is set to 0, so the thread is reclaimed once it is idle, so there are no idle threads.

  • CachedThreadPool: cacheable thread pool.
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
Copy the code

CachedThreadPool has a core thread count of 0, does not create a core thread, but directly adds tasks to the queue, and has a maximum number of threads integer. MAX_VALUE, which means that non-core threads can be created indefinitely to process tasks. It also has a thread idle timeout of 60 seconds, and idle threads can be cached for 60 seconds before being reclaimed.

  • SingleThreadExecutor: thread pool that works with a single thread.
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
Copy the code

The number of core threads in SingleThreadExecutor is 1, and the maximum number of threads is 1, so there is only one core thread working in SingleThreadExecutor thread pool, and the idle timeout is 0. Submitted tasks are placed in the LinkedBlockingQueue task queue to be executed one by one.

  • ScheduledThreadPool: a thread pool that implements scheduled and periodic tasks.
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
Copy the code

The ScheduledThreadPool has a fixed number of core threads, and the maximum number of threads is integer. MAX_VALUE. The DelayedWorkQueue is an unbounded queue. The thread pool only keeps adding tasks to the task queue, with a timeout of 10 seconds for idle threads.

7,

So that’s the basics of multithreading in Android. You need to pay attention to the use of multithreading, not only to ensure that threads are synchronized in Android but also to be aware of memory leaks. Operations that require frequent child thread creation are best managed using thread pools.

References:

Android Advanced Light in-depth understanding of the Java virtual machine