DEV Community

banu
banu

Posted on

Threads Explained: Simplest Manner.

Threads in programming | devobee (AI Generated)

Thread is an easy concept that anybody can grasp upon. It is a light weight sub process that runs along side any main process. It’s like a separate path of execution within a program. A thread can be run alongside the main method or the function.

Let’s understand it simply first,

Threads can be thought of as tasks that run within the same process, sharing the same memory and resources.

A good way to imagine this is by comparing it to a web browser. While each tab in modern browsers often run as a separate process, the tasks inside that tab like rendering the page, handling user input, or playing a video run as multiple threads within the same process.

Multiple Tabs | fastcompany.com

Note: Here I will refer both method and function as method since it is highly used term in high level languages .

Normally, in most of the high level languages like C, C# and Java, the main method that we declare executes first. That’s the main process in the system. What if we are required to run a method alongside the main process like, loading data from the database or an API? That’s where thread comes in like a hero. Instead of making the main method wait until fetching data, we can run a separate execution process to fetch the data in the background. This helps to increase the performance and User Experience of the application.

In real world systems like Windows, there are threads which executes in the background and those threads are managed by an application called “scheduler”.

Windows Scheduler | digitalcitizen.life

It manages thread scheduling and execution of it, inside the windows operating system. You can learn more about it in here.

Likewise in each high level programming language there’s a scheduler or a runtime environment that interacts with the operating system’s thread scheduler to manage how threads are executed. (In java its JVM, Java Virtual Machine)

Let’s take a code example of a thread declaration.

Here I will use java as the programming language since it is one of the easiest languages to explain apart from other high level languages.

public class Main {
    public static void main(String[] args) {
        // Easiest way to declare a thread
        Thread a1 = new Thread(() -> {
            System.out.println("A is running");
        });
        Thread a2 = new Thread(() -> {
            System.out.println("B is running");
        });
        a1.start(); // Schedules the thread to run concurrently
        a2.start(); // Schedules the thread to run concurrently
        System.out.println("End of the main method");
    }
}
Enter fullscreen mode Exit fullscreen mode
A is running
End of the main method
B is running
Enter fullscreen mode Exit fullscreen mode

In the above code, the threads a1 and a2 executed alongside the main method. That’s why even though the main method ended the a2 method still got executed. The main method does not wait until the thread finishes which shows that threads run “alongside” the main method.

Note: the threads can execute differently at each execution time, sometimes it might execute before the main method. If we run the above code again this result can be observed .

End of the main method
B is running
A is running
Enter fullscreen mode Exit fullscreen mode

Key point here is, A thread can run independently of the main method.

Thread Priority

By setting a thread priority it doesn’t actually runs first or last, what it really does is instead, gives a suggestion to the scheduler, “Hey scheduler, this thread is a high priority thread, check it out.” What scheduler does is finds out the time and the resource it takes and then executes it.

It’s like a restaurant, where the waiter is the scheduler, chef is the CPU and the order (in this case a burger) is a thread.

Waiter suggests this burger is for a VIP guest, and the chef starts cooking it immediately while cooking for other guests and serve it faster than other orders. But this doesn’t mean the chef only cooks it first and then cooking other orders later, instead the chef gives some priority to the VIP order to make it faster than the regular orders.

Restaurant Image | AI Generated

In Java we can set a thread priority like this,

public class Main {
    public static void main(String[] args) {
        Thread vipOrder = new Thread(() -> {
            System.out.println("Preparing VIP order...");
        });
        Thread regularOrder = new Thread(() -> {
            System.out.println("Preparing regular order...");
        });

        vipOrder.setPriority(Thread.MAX_PRIORITY); // 10
        regularOrder.setPriority(Thread.MIN_PRIORITY); // 1

        vipOrder.start();
        regularOrder.start();
        System.out.println("End of the main method");
    }
}

Enter fullscreen mode Exit fullscreen mode
Preparing VIP order...
End of the main method
Preparing regular order...
Enter fullscreen mode Exit fullscreen mode

After running it twice,

End of the main method
Preparing VIP order...
Preparing regular order...
Enter fullscreen mode Exit fullscreen mode

This proves that the VIP order gets a higher priority than any other threads.

Here’s how the system identifies the importance of the thread level.

The default priority is identified as 5, while the minimum is 1 and maximum is 10. The default priority is already set to every thread.

Thread.MIN_PRIORITY // 1
Thread.NORM_PRIORITY // 5, No need of defining it since it's default
Thread.MAX_PRIORITY // 10
Enter fullscreen mode Exit fullscreen mode

We normally use multiple threads in a system to manage and access resources and data. A good example is accessing and modifying data from a database.

But here’s the thing, let’s say multiple threads tried to access the same thread and modify it at the same time. This is going to cause conflicts.

Here’s an example in real life,

There are two customers (threads) Alice and Bob, who both rush to take a donut from a tray at the same time, but only one donut remains. What can happen here?

Either one of them picks it up. Both think, “I got it”,but in reality only one gets the donut. This leads to a conflict between the Alice and Bob.

Alice and Bob | AI Generated

Likewise in programming this can lead to data inconsistency (same data, shows differently or incorrectly).

public class DonutShop {
    private int donuts = 1;

    public void buyDonut(String customer) {
        if (donuts > 0) {
            System.out.println(customer + " is buying a donut...");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            donuts--;
            System.out.println(customer + " bought a donut! Remaining: " + donuts);
        } else {
            System.out.println(customer + " found no donuts left!");
        }
    }

    public static void main(String[] args) {
        DonutShop shop = new DonutShop();

        Thread t1 = new Thread(() -> shop.buyDonut("Alice"));
        Thread t2 = new Thread(() -> shop.buyDonut("Bob"));

        t1.start();
        t2.start();
    }
}
Enter fullscreen mode Exit fullscreen mode
Alice is buying a donut...
Bob is buying a donut...
Alice bought a donut! Remaining: 0
Bob bought a donut! Remaining: -1
Enter fullscreen mode Exit fullscreen mode

In the above code the donut count can’t be -1, since in real life a negative value of an item doesn’t exist which is “corrupted data”.

To solve this issue we use synchronize keyword. By using this keyword we can synchronize or coordinate with the data. In the above scenario a waiter can give the donut to one of the customers Alice and Bob. Let’s say that Alice got it, now the waiter (coordinate) can say to Bob that there are “no donuts left in the tray”. This helps to reduce data inconsistency.

This is how we apply synchronization in programming

public class DonutShop {
    private int donuts = 1;

    public synchronized void buyDonut(String customer) {
        if (donuts > 0) {
            System.out.println(customer + " is buying a donut...");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            donuts--;
            System.out.println(customer + " bought a donut! Remaining: " + donuts);
        } else {
            System.out.println(customer + " found no donuts left!");
        }
    }

    public static void main(String[] args) {
        DonutShop shop = new DonutShop();

        Thread t1 = new Thread(() -> shop.buyDonut("Alice"));
        Thread t2 = new Thread(() -> shop.buyDonut("Bob"));

        t1.start();
        t2.start();
    }
}
Enter fullscreen mode Exit fullscreen mode
Alice is buying a donut...
Alice bought a donut! Remaining: 0
Bob found no donuts left!
Enter fullscreen mode Exit fullscreen mode

Thread Daemon

Thread daemon is a service thread that runs behind the application (Background thread). Daemon provides services (supports) all non daemon threads. The main purpose of thread daemon is to perform tasks that should not block the program from exiting.

As an example let’s take the previous bakery shop, closes for the day after the last customer leaves. There’s a background music system (daemon) that plays soft music. Once the staff leave, the bakery locks the doors, and the music automatically stops. The staff didn’t wait for the music to finish because it’s not essential.

Normally when running non daemon threads (threads like above) the program exists once the main thread and non daemon threads finish executing, but daemon threads doesn’t prevent the program from exiting.

public class BakeryMusicDaemon {
    public static void main(String[] args) {
        // Background music thread (daemon)
        Thread music = new Thread(() -> {
            while (true) {
                System.out.println("Playing soft background music...");
                try {
                    Thread.sleep(1000); // Simulate music playing
                } catch (InterruptedException e) {
                    break;
                }
            }
        });

        music.setDaemon(true); // Mark as daemon
        music.start();

        // Staff finishing their work
        Thread alice = new Thread(() -> System.out.println("Alice finishes work and leaves."));
        Thread bob = new Thread(() -> System.out.println("Bob finishes work and leaves."));

        alice.start();
        bob.start();

        // Wait for staff to leave
        try {
            alice.join();
            bob.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Manager closes the bakery. Background music stops automatically.");
    }
}
Enter fullscreen mode Exit fullscreen mode
Playing soft background music...
Alice finishes work and leaves.
Bob finishes work and leaves.
Manager closes the bakery. Background music stops automatically.
Enter fullscreen mode Exit fullscreen mode

Daemon threads are usually used for tasks like, garbage collection, monitoring resources and logging.

That’s pretty much it for learning about threads. We can go more into detail about threads like about Multi-threading, thread pool which I hope to write a post later.

Thanks for reading, write your thoughts below ✌️

banu.

Top comments (0)