As you remember from the previous topic, each program has at least one thread — the main thread. The main thread is a starting place from which you may spawn new threads to perform your tasks. To do that, you have to create your own threads, write code to be executed in a separate thread, and then start it.
Create custom threads with inheritance
Two primary ways to create a new thread that performs a task you need are the following:
extending the
Threadclass and overriding itsrunmethod;
implementing the
Runnableinterface and passing the implementation to the constructor of theThreadclass.
The code below gives an example of extending the Thread class and overriding its run method:
class HelloThread : Thread() {
override fun run() {
val helloMsg = "Hello, i'm $name"
println(helloMsg)
}
}
And the following code shows how to implement the Runnable interface and pass the implementation to the constructor of the Thread class:
class HelloRunnable : Runnable {
override fun run() {
val threadName = Thread.currentThread().name
val helloMsg = "Hello, i'm $threadName"
println(helloMsg)
}
}
In both cases, you should override the run method, which is a regular method and contains code to perform a task. Which approach to choose depends on the task and on your preferences. If you extend the Thread class, you can accept fields and methods of the base class but you cannot extend other classes, since Kotlin doesn't have multiple inheritance of classes.
As you may know, the Thread class has a lot of constructors. You may find a full list here. We will use some of them to demonstrate custom thread creation:
val t1 = HelloThread() // a subclass of Thread
val t2 = Thread(HelloRunnable()) // passing runnable
And here's another way to specify the name of your thread by passing it to the constructor:
val myThread = Thread(HelloRunnable(), "my-thread")
If you want to specify the name of the thread in the HelloThread class, you should override the constructor. So, the Runnable interface provides you a more versatile way to work with threads.
If you are already familiar with lambda expressions, you may do the whole thing like this:
val t3 = Thread {
println("Hello, i'm ${Thread.currentThread().name}")
}Easy way to create a thread
But why do we need to implement an interface or extend a class to create an almost standard thread?
You may create a thread with the function thread(...): Thread. In this case, the executing code is passed in the block argument:
import kotlin.concurrent.thread
val t4 = thread(start = false, name = "Thread 4", block = {
println("Hello, I'm ${Thread.currentThread().name}")
})
This function may have a lot of parameters that will describe your custom thread:
start– iftrue, the thread is immediately started (then the thread will be created);isDaemon– iftrue, the thread is created as a daemon thread (we will talk about them later);contextClassLoader– a class loader for loading classes and resources in this thread (you will learn more about this later);name– the name of the thread;priority– the priority of the thread;block– thread's execution code.
The function thread() is from the kotlin.concurrent package, don't forget to import it!
Now you've created objects for threads, but you're not done yet. To perform the tasks you need, you'll have to start your thread.
Starting threads
The class Thread has a method called start(), which is used to start a thread. At some point, after you invoke this method, the method run will be invoked automatically, but it won't happen immediately.
Let's suppose that inside the main function, you create a thread named t using the thread() function and then start it.
fun main() {
val t = thread(start = false, block = {
println("Hello, I'm ${Thread.currentThread().name}")
})
t.start()
}
Also, if you set the value of the start parameter to true (it's the default value), you can start the thread without invoking the method start():
fun main() {
val t = thread(block = {
println("Hello, I'm ${Thread.currentThread().name}")
})
}
Eventually, in both cases the result will be:
Hello, i'm Thread-0
We've looked at how you can start a thread to run. Now, let's take a deeper look and see how starting a thread works from the inside.
Thread working
Here's a picture that explains how a thread actually starts and why it is not happening immediately.
As you may see, there is some delay between starting a thread and the moment when it really starts working (running).
By default, a new thread runs in the non-daemon mode. Reminder: the difference between the daemon and the non-daemon mode is that the JVM will not terminate a running program while there're still non-daemon threads left, while the daemon threads won't prevent the JVM from terminating. So, daemon threads usually do some background job.
Do not confuse the methods run and start. You must invoke start if you'd like to execute your code inside run in another thread. If you invoke run directly, the code will be executed in the same thread.
If you try to start a thread more than once, start throws IllegalThreadStateException.
Despite the fact that within a single thread all statements are executed sequentially, it is impossible to determine the relative order of statements between multiple threads without additional measures that we will not consider in this lesson.
Consider the following code:
fun main() {
val t1 = HelloThread()
val t2 = HelloThread()
t1.start()
t2.start()
println("Finished")
}
The order of displaying messages may be different. Here is one example:
Hello, i'm Thread-1
Finished
Hello, i'm Thread-0
It is even possible that all threads may print their text after the main thread prints "Finished":
Finished
Hello, i'm Thread-0
Hello, i'm Thread-1
This means that even though we call the start method sequentially for each thread, we do not know when the run method will be actually called.
Do not rely on the order of execution of statements between different threads unless you've taken special measures.
A simple multithreaded program
Let's consider a simple multithreaded program with two threads. The first thread reads numbers from the standard input and prints out their squares. At the same time, the main thread occasionally prints messages to the standard output. Both threads work simultaneously.
Here is the thread that reads numbers in a loop and squares them. It has a break statement to stop the loop if the given number is 0.
class SquareWorkerThread(name: String) : Thread(name) {
override fun run() {
while (true) {
val number = readln().toInt()
if (number == 0) {
break
}
println(number * number)
}
println("$name's finished")
}
}
Inside the main function, the program starts an object of the SquareWorkerThread class, which writes messages to the standard output from the main thread.
fun main() {
val workerThread = SquareWorkerThread("square-worker")
workerThread.start() // start a worker (not run!)
for (i in 0 until 5_555_555_543L) {
if (i % 1_000_000_000 == 0L) {
println("Hello from the main!")
}
}
}
Here is an example of inputs and outputs (with added comments):
Hello from the main! // the program outputs it
2 // the program reads it
4 // the program outputs it
Hello from the main! // outputs it
3 // reads it
9 // outputs it
5 // reads it
Hello from the main! // outputs it
25 // outputs it
0 // reads it
square-worker finished // outputs it
Hello from the main! // outputs it
Hello from the main! // outputs it
Process finished with exit code 0
As you can see, this program performs two tasks "at the same time": one in the main thread and the other one in the worker thread. It may not be "the same time" in the physical sense; however, both tasks are given some time to be executed.
Conclusion
In this topic, you've learned how to create an object of a thread, set the code which this thread should execute, and start the work of the thread. Also, you've found out how the thread works from the inside, as well as other important aspects of threads. We hope that this topic was useful, and now let's practice to better assimilate what we have learned!