Android Thread 101 (Part I)— What is a Thread ?

Jimmy Liu
Kuo’s Funhouse
Published in
6 min readMar 11, 2022

I am writing this article to help myself and others to get a better understanding of thread in Android.

Here are some of the topics that will be covered in this article.

Table of Content

  • What is a Thread ?
  • Why do we need Thread ?
  • How to create Thread ?
  • Thread and Runnable

What is a Thread ?

There are actually two types of threads :

  • OS Thread
  • User Thread

Based on OS Concept [1]:

A thread is a basic unit of CPU utilization. It is comprises of Registers, Program Counter, ThreadID and a Stack.

This is the definitions of an OS Thread, also known as Kernel-Level Thread or Kernel Thread. These are the threads that are controlled by the Kernel.

In contrast, a User-Level Thread or User Thread, is a thread that can be controlled by users.

In order for the kernel to proceed operations defined in a user thread, each user thread must be passed into a kernel thread after being created.

Depending on the OS, a single kernel thread can be responsible for :

  • Single User Thread (1:1)
  • Many User Threads (1..N : M)
  • All User Threads (All : 1)

However, as technology advanced, most modern OS takes on the 1 to 1 relationship approach, which provides more concurrency than All to 1 relationship and easier to implement than 1..N:M relationship.

Just to help you understand the differences between concurrent execution and parallel execution, here are two images from OS Concept [1], respectively

Concurrent : Threads or Tasks being executed concurrently (lined up in an array) by a single core [ 1 ]
Parallel : Threads or Tasks being executed parallelly (via multiple cores) [ 1 ]

User Thread in Java

In Java, a user thread is defined as a Thread class, which is an implementation of Runnable. More details on their roles later.

http://www.plantuml.com/

Why do we need Thread ?

When running an app, in order to interact with users responsively, a thread called UI Thread or Main Thread is used solely for this purpose, including redrawing the screen, responding to user inputs, and more.

However, there are times when we need to do some heavy computation which might block the UI thread if they were to be performed on the UI thread, causing ANR (application not responding).

In Android, when a response time takes more than 5 seconds or a Broadcast that is unable to be finished within 10 seconds , it is considered as an ANR (application not responding).

In order to avoid ANR from happening, we need to perform heavy computations on a background thread, which is basically any threads that is not the Main Thread.

ANR

To illustrate how ANR can occur, let’s take a look at the code in a tutorial by Coding in Flow, How to Start a Background Thread in Android .

Here is the app UI in the tutorial, which is composed of two button and a switch:

In order to create an ANR, startThread() will be called when btn_start_thread is clicked, which will then triggered Thread.sleep(int).

Thread.sleep(int) will block the current Thread that this function is called. In this case, the UI Thread will be blocked for a certain amount of time, which is set to 1 second or 1000 ms.

private fun startThread(view: View) {
for (i in 0 until 10) {
try {
Log.d(TAG, "start Thread: $i")
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}

Upon calling startThread(), you can try to trigger the switch, which you might find it not responding, because UI Thread is now blocked.

Since it takes 10 seconds to finish startThread loop, the waiting time for the UI or switch to response might take more than 5 seconds, depending on the time you trigger the switch. This will result in an ANR error.

So how do we fix it ?

We can simply create a Thread that do what startThread is doing.

How to create a Thread ?

A Thread can be created in two ways :

  1. Extends the Thread Class
class MyThread(seconds: Int) : Thread() {    private var _seconds: Int = seconds    override fun run() {
for (i in 0 until _seconds) {
try {
Log.d(TAG, "start Thread: $i")
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
MyThread(10).start()

2. Implement a Runnable and pass it into a Thread Object

inner class MyRunnable(seconds: Int): Runnable {    private val _seconds = seconds    override fun run() {
for (i in 0 until _seconds) {
try {
Log.d(TAG, "start Thread: $i")
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
Thread(MyRunnable(10)).start()

Comparing the two methods, implementing a Runnable is better if you are concerned with reusability and decoupling.

Now, by calling these functions :

private fun startThread(view: View) {

// Creating Thread via Subclass
MyThread(10).start()
// Creating Thread via Runnable
Thread(MyRunnable(10)).start()
// using kotlin.concurrent
// default start == true, thread will start immediately
thread(start = false) {
for (i in 0 until _seconds) {
try {
Log.d(TAG, "start Thread: $i")
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}.start()
}

The UI will no longer be blocked.

So what is the relationship between a Thread and Runnable ?

Thread and Runnable

As shown above, Thread is a class that implements Runnable, which is a Single Method Interface that has an abstract function called run().

http://www.plantuml.com/

Let’s take a look at how Thread implement Runnable:

@Override
public void run() {
if (target != null) {
target.run();
}
}

So basically, in Thread, run() will perform target.run() if the target: Runnable exist in the Thread Object.

However, a Runnable does not create thread, instead, we need to call start() in order to create an OS thread.

public synchronized void start() {
/// ...
// add current Thread to a ThreadGroup
group.add(this);

try {
nativeCreate(this, stackSize, daemon);
started = true;
} finally {

try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}

Synchronized methods within an object can only be called one at a time by a single thread at any time.

By stating start() as an synchronized method will stop us from creating multiple threads with the same Thread object, which will result in systematic errors.

In start(), it performs the followings :

  1. add the current thread to ThreadPool (a set of Threads)
  2. create a native thread by calling nativeCreate (the native modifier indicates that this method is implemented in native code using JNI, Java Native Interface [2])
  3. then finally, set the boolean start = true

Among these instructions, the one we are most interested is nativeCreate.

By examining the source code from [3] and [4], we know that nativeCreate will call a function in runtime/native/java_lang_Thread.cc, which will end up creating a pThread, this is also where Thread#run() is called.

Here is a brief summary of what happened when nativeCreate() is called :

Brief walkthrough of how Thread is created, more info can be found in [ 3 ], [ 4 ]

A pthread, aka POSIX thread, is created using the POSIX thread libraries, which are a standards based thread API for C/C++ and it is used in Linux OS.[5]

Now that we know how a thread is created, you might wish to know how and when is a thread being proceeded. However, this depends on the strategy used in thread scheduling, which is something that we don’t know of.

Reference

[ 1 ] Operation System Concepts, 10th ed., Silberschatz A., Galvin P.B. and Gagne G., 2018

[ 2 ] native keyword java

[ 3 ] java_lang_Thread.cc

[ 4 ] Thread.cc

[ 5 ] POSIX thread (pthread) libraries

--

--

Jimmy Liu
Kuo’s Funhouse

App Developer who enjoy learning from the ground up.