- Published on
Android Thread: Must-Know Knowledge (P1)
- Authors
- Name
- Dan Tech
- @dan_0xff
Once you have knowledge of the Android Lifecycle, the next thing you need to grasp is Android Thread. Although with modern programming styles we no longer directly use Thread, understanding and mastering the knowledge of Android Thread is still a prerequisite for an Android Engineer.
Thread Concept in Programming
Concept
A thread is a unit of execution in a program. Each thread can only perform one task at a time. A program that utilizes multiple threads executing at the same time can handle multiple tasks simultaneously.
Currently, Android programming can be easier as we don't directly manipulate threads but operate through techniques like Executor, RxJava, or Coroutines... However, it must be emphasized that all these techniques are built on threads. Understanding threads will help you feel more confident in your work and development.
Thread lifecycle
You can find a picture like the one above on the Internet to simulate the lifecycle of a thread. It's not wrong, but in my Android programming course, everything will be simplified for the easiest understanding and absorption for learners.
Imagine a thread as a water pipe. You pour water from one end, and the water flows through to the other end and comes out. That's a thread's lifecycle.
- New Thread: This is the process of preparing resources to initialize a thread according to the programmer's needs. This process is quite resource-intensive. Therefore, in professional programming, people usually don't spam new threads; instead, they reuse threads to avoid waste.
- Run: This is the process of executing a specific piece of code on the newly created thread. The speed of this execution depends on the logic you pass in, whether it locks other threads or not. This is the logic of the program you write.
- Terminated: This is the process of cleaning up and killing a thread that has finished its task, so the program can reclaim memory. When the thread reaches this point, the scenario could be that your input logic in Run has been executed completely.
- Or the thread encounters an Exception during execution, leading to it being Terminated.
Android Thread: Main Thread vs Background Thread
Threads in Android operate similarly to the knowledge I mentioned above. However, Android provides more tools to manage tasks running on threads, including: Handler, Looper, HandlerThread.
Main Thread
This is the main thread, allocated by default to an Android program. This thread is responsible for handling tasks such as: Updating the user interface (UI), receiving and processing user events (Touch Input, Click Button, Input Text).
Data processing, reading, and writing tasks should not be executed on the Main Thread as it will cause the app to freeze, or even crash.
Background Thread
This is a thread created specifically to perform heavy tasks without affecting the user experience or the Main Thread.
In each application, we can create multiple Background Threads, and multiple Loopers, Handlers, and HandlerThreads.
- Looper is a component created by Android. Its purpose is to create an infinite loop to keep a Thread from falling into the Terminated state. Each Looper has a MessageQueue to handle tasks that are pushed in and executed sequentially. Each Looper serves only one specific Thread. The Looper that serves the Main Thread is Looper.getMainLooper().
- Handler is a class provided by Android. Handlers are used to put tasks or events into the Looper's MessageQueue. The Looper then retrieves each Message in the MessageQueue and executes it on the corresponding Thread.
- HandlerThread is a complete wrapper including the trio: Thread, Looper, and Handler. HandlerThread creates will execute tasks on a Background Thread (because the Main Thread is created once and only once when the application starts). Some notes when using HandlerThread can be found in the course's source code.
// Declare Handler for Main Thread (UI Thread)
private val uiHandler: Handler = Handler(Looper.getMainLooper())
// Declare HandlerThread for a Background Thread "Background-HandlerThread"
private val backgroundHandlerThread = HandlerThread("Background-HandlerThread").apply {
start()
}
private val backgroundHandler: Handler by lazy { Handler(backgroundHandlerThread.looper) }
private fun runUITask() {
uiHandler.post {
// Run UI Task
Log.d("MainActivity", "Run UI Task ${Thread.currentThread().name}")
}
}
private fun runBackgroundTask() {
backgroundHandler.post {
// Run Background Task
Log.d("MainActivity", "Run Background Task ${Thread.currentThread().name}")
}
}
Example of how to use Handler, HandlerThread in Android - Android Mastery by Dan Tech
Let me reiterate and emphasize. Nowadays, the use of Handler and HandlerThread is relatively rare in new projects due to the emergence of many powerful supporting libraries such as Rx Java, Kotlin Coroutines. However, all these libraries deep dive inside them are executed on Threads. Therefore, learning and understanding how a Thread works is extremely important to adapt to all new technologies in the future.
Deadlock in Asynchronous Programming
Deadlock is a situation when two or more threads are waiting to use a resource. But this resource is stuck in another thread that cannot be released. This resource freeze can recur in many different ways. But I will give you a classic example of Deadlock for you to easily understand.
data class UserData(val name: String, val message: String, var money: Int)
private var resource1: UserData = (UserData("User1", "Hello From User1", 200))
private var resource2: UserData = (UserData("User2", "Hello From User2", 200))
private val handlerThread1 = HandlerThread("HandlerThread1").apply {
start()
}
private val handlerThread2 = HandlerThread("HandlerThread2").apply {
start()
}
fun simulateDeadlock() {
val handler1 = Handler(handlerThread1.looper)
val handler2 = Handler(handlerThread2.looper)
Log.d("SimpleFragment", "resource1 $resource1")
Log.d("SimpleFragment", "resource2 $resource2")
handler1.post {
synchronized(resource1) {
resource1.money -= 100
Log.d("SimpleFragment", "handler1 synchronized resource1 $resource1")
synchronized(resource2) {
resource2.money += 100
Log.d("SimpleFragment", "handler1 synchronized resource2 $resource2")
}
}
}
handler2.post {
synchronized(resource2) {
resource2.money -= 100
Log.d("SimpleFragment", "handler2 synchronized resource2 $resource2")
synchronized(resource1) {
resource1.money += 100
Log.d("SimpleFragment", "handler2 synchronized resource1 $resource1")
}
}
}
}
Example of Deadlock in Android Thread - Android Mastery by Dan Tech
The above example simulates the process of transferring and receiving money between 2 Users, but because it is performed on 2 different Threads, Deadlock occurs when 2 Threads are waiting for each other's resources and neither side can release them first.
To overcome the Deadlock problem, there are many solutions, depending on the business logic, we can choose one of the following ways to handle it:
- Put everything into a common MessageQueue for processing. This is very simple. Because when processed sequentially, the Threads will no longer compete for each other's resources.
- Rearrange the logical order so that Deadlock scenarios cannot occur.
You can refer to my GitHub source code here