Contents

Java Concurrency The Complete Basics Part - 1

Understanding Concurrency - Meaning and concepts

“Concurrency is the ability to run several programs or several parts of a program in parallel.” This definition is broad but let’s stick with it for now, it’s simple.

Concurrency for our systems

Computer systems can do more than one thing at a time. For example, currently while scrolling through this article on your browser, your system is running other various background services, like downloading your requested files over network, or playing the music you have been running on background, responding to the keyboard inputs, to tracking mouse events or rendering and displaying various things on your screen. Concurrency is a part of the system itself, it’s the powerful features of our computer operating system.

But here, we will be learning about “What, Why and How” on Concurrency with Java. These concepts will be an excellent foundation for building your own small to large scale concurrent application in the future.

It’s a huge subject itself, so on some topics rather than diving deep into rabbit-hole I will be leaving some points with [See: for yourself] to explore. If you are a complete beginner, you may have to read this several times. But, for everyone we will be rolling through the basics here. Let’s get started !!!

System and Hardware

Parallel execution on a single core CPU and multi core CPU is a bit different. A single core gives us feels of parallel execution by multitasking (using context switches- we will talk about this), whereas multi cores can perform a multiple task executing them at the exact same time achieving true parallelism.
[See: Concurrency Vs Parallelism are used in relation to each other in multithreaded environment]

OS Model and Process

Another basic unit you need to understand, for task execution is: processes and threads. Thread is an independent task, with its own data heap, stack and program counter. It executes and functions a single unit. Or please refer to the JavaDocs on this.
https://docs.oracle.com/javase/tutorial/essential/concurrency/procthread.html

/java-concurrency-the-complete-basics-part1/osprocessnthreads.png
Os Process and Threads

OS Thread Execution

Context Switches and Scheduling

Context switches means jumping from executing one thread to executing another. As you can see from the above “process and thread” block diagram, each thread consists of its own local data and Instruction pointer (or program counter) and also other stuff. So, on every thread switch it needs to maintain the information; like for current thread - save local data, save Instruction pointer then for the next thread load current data, continue from previous Instruction pointer.

In short and simple… (lets say we have a program)

  • Schedule thread 1 in
  • Start thread 1
  • Stop thread 1
  • Schedule thread 1 out
  • Schedule thread 2 in
  • Start thread 2
  • Stop thread 2
  • Schedule thread 2 out
  • Schedule thread 1 in

You got the idea..
[See: Also See: Thread lifecycle and Thread datas ]

Threads are some long running, some quick task and some uncontrolled IO operations.

Each of them executed concurrently, quicker gets finished and are removed and longer task executes from time-time until completion. So, now let’s assume we have about 100’s of thread currently running. Now, there’s going to be a lot and lots of context switches. So it’s bad, the right number of threads and proper scheduling is vital.

Cons
Too many threads causes more time in thread management than performing real work. And consumes a lot of resources, cpu and memory.
[See: Ideal/optimal thread count vs CPU cores].

Often to deal with such problems, there are various concepts, one is maintaining the optimal numbers of thread count and the other is using an efficient “Thread scheduling Algorithm”.

Scheduling
As you can see from the above diagram, the thread scheduler is the one that manages the context switches.
[For more See: Operating system thread scheduler]
https://web.cs.ucdavis.edu/~pandey/Teaching/ECS150/Lects/05scheduling.pdf

Here’s the list of some scheduling algorithms:-

  • FCFS - First come first serve - ( cons: long thread causes starvation )
  • SJF - Shortest job first - ( cons: longer task may not get cpu time )
  • Priority scheduling - ( address problems with static and dynamic scheduling )

So now, we know that that using multi-threading automatically can’t make an application go faster. And it depends upon multiple factors:

  • Computer has a fixed number of cores (or hyperthreads).
  • Each thread has to be scheduled to a core or hyperthread in order to run.
  • If there are more runnable threads than (available) cores / hyperthreads, they must wait.
  • Each thread has its own memory regions, typical (default) thread stack size is 512Kbytes or 1Mbytes. More threads means significant memory usage and management
  • The overhead of switching between threads also relates to OS thread scheduling decision.
Checkpoint
Okay ! Great. Let’s summarize what we have learned this far. We understood - What concurrency is ?, How concurrency is achieved in single core vs multicore ? processes and threads in OS model, What context switch is and how does it happen ? Thread Lifecycle, Ideal thread size/count, Scheduling algorithms for context switch and much more concepts you have explored on with [ See ] labels.

I think we are now good to go. We will keep exploring the more fundamental concept as required throughout the course - Step by Step.

Lets get started !!!



Thread Basics in Java

The thread concept is very fundamental in java. It was first introduced with basic concurrency support since version 5.0 in the java.util.concurrent packages

A HelloWorld Program:

Even the Hello World program with a main() method executes in a thread, and it is referred to as the “main thread”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class MainClass {

    public static void main(String[] args) {
        System.out.println("Hello World");
        System.out.println("My name is: " + Thread.currentThread().getName());
        System.out.println("My id is: "+ Thread.currentThread().getId());

        // output
        // Hello World
        // My name is: main
        // My id is: 1
    }
}

1. Thread Creation

There are two main approaches to creating a thread in Java.

  1. Extending Thread class
  2. Implementing Runnable Interface

Lets see this in code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

public class A1_ThreadCreation {

    public static void main(String[] args) {
        System.out.println("01. Java Concurrency -- Thread Creation");

        // 1. "extends Thread"
        HelloThread helloThread = new HelloThread();
        helloThread.start();

        // 2. implements Runnable
        HelloThreadTwo helloThreadTwo = new HelloThreadTwo();
        Thread threadRunnable = new Thread(helloThreadTwo);
        threadRunnable.start();


        // Another common approach of thread creation you will often see, 
        // Passing Anonymous Runnable Object as an argument to thread
        Thread threadAnonymous = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from thread created Using Anonymous Runnable Object");
            }
        });
        threadAnonymous.start();

        // Another approach, Transforming above code into Lambda functional interface
        Thread threadLambda = new Thread(() -> System.out.println("Hello from thread created Using Functional Interface"));
        threadLambda.start();

    }

    // 1. Extending Thread Class
    public static class HelloThread extends Thread {

        @Override
        public void run() {
            System.out.println("Hello from a Extends Thread implementation!");
        }

    }

    // 2. Implementing Runnable Interface
    public static class HelloThreadTwo implements Runnable {

        @Override
        public void run() {
            System.out.println("Hello from a Runnable thread Implementation!");
        }

    }

}

Code Analysis:

  1. Main thread starts
  2. Queues
    • 1- “helloThread”,
    • 2- “helloThreadTwo”,
    • 3- “threadAnonymous”,
    • 4- “threadLambda”
    • (we have 4 threads + 1 (main thread) = 5 threads in total)
  3. Starts all four threads in background, start() calls run() method in seperate thread.
    • Since JVM first queues thread and later starts processing,
    • The thread can complete execution in different order depending on queuing and its task completion.
    • Any running thread that completes their run() method at any time is stopped. (i.e removed from thread scheduling queue)
  4. If the tasks are longer they keeps executing in the background even after the main thread completes.
    • If not handled properly, this can lead to anomalies, like sometimes, a multiple and complex relation between threads causes ( **Deadlock, Race conditions, Data race, infinite loop or uncontrolled operations **- we will discuss on these)

[See: Runnable Interface vs Extending a Thread, Java Memory Model etc.. ]

2. Thread class methods

Each thread has its own information and behaviours defined by java.

  • Thread Information: getId(), getPriority(), getName() etc..
  • Behaviours: isAlive(), sleep(), start(), join(), wait, notify, isInterrupted() etc..

Interrupting Running Thread

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class A2_ThreadInterrupts {

    public static void main(String[] args) throws InterruptedException {


        SomeTask someTask = new SomeTask();
        Thread threadSomeTask = new Thread(someTask);

        // Our thread "threadSomeTask" will continue running on background. So, if we want to stop it when main thread stops, attached it to daemon
        // threadSomeTask.setDaemon(true);

        threadSomeTask.start(); // start() method calls run(), in a new thread, if you directly call run() method then it wont me a new thread

        /**
         * Lets wait for 3 seconds and call `intrrupt()` to stop our thread
         *
         * We know that main thread runs as a thread itself, (so, mainthread is already running)
         * we also have already started our other thread "threadSomeTask" in the background.
         * so calling Thread.sleep() here will only pause our main thread
         */
        Thread.sleep(3000);
        threadSomeTask.interrupt();

        // --------------------------------------------------------------------------------

        LongProcessingTask longProcessingTask = new LongProcessingTask();
        Thread threadLongProcessing = new Thread(longProcessingTask);
        threadLongProcessing.start();

        /**
         * Here interrupt will only work with conditions, interrupt can only schedule to stop the thread for the next coming cycle.
         * Previously, we did thread.sleep(), that "scheduled thread out".
         * And then calling interrupt method, marked it as not eligible for the context switches and was removed from scheduling queue; throwing "InterruptedException"
         */
        threadLongProcessing.interrupt();

    }

    public static class SomeTask implements Runnable {

        @Override
        public void run() {
            System.out.println("----------- SomeTask ---------------");
            System.out.println("Executing some task in background ");

            try {
                System.out.println("Performing some operation. May take 10-20 Seconds !!!");
                // sleep() method "Scheduled thread out" for a time being eg. 10 Second here (during this other task comes in),
                // After 10 seconds the thread scheduler, "Reschedules thread in" using context switches
                Thread.sleep(10000);
                System.out.println("This task is executed after waiting for 10 seconds");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static class LongProcessingTask implements Runnable {

        @Override
        public void run() {

            System.out.println("----------- LongProcessingTask ---------------");
            System.out.println("I cant be interrupted. ( Need some conditions to stop the flow and 'Scheduled me out' ). ");
            while (true) {
                System.out.println("... I am performing Infinite sout ... ");

                // Now, This thread can be stopped here, By checking if interrupt was called
                if (Thread.interrupted()) {
                    System.out.println("Condition Met !!! I am interrupted.");
                    // some task to carry out on an interruption
                    return;
                }
            }
        }
    }

}

Thread Methods - join()

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
public class A2_ThreadMethods {

    public static void main(String[] args) throws InterruptedException {
        /**
         * Inter-thread communication- See thread methods: join(), wait()-notify().
         *
         * Lets send an SMS to our friend and then ony check our balance
         * Lets create a accountInfo first, it will hold "accountHolderName" and "currentBalance"
         */

        AccountInfo accountInfo = new AccountInfo("CodeGrave", 200);
        // Initializing thread - sendSMS and checkBalance
        SendSMS sendSMS = new SendSMS(accountInfo);
        Thread smsThread = new Thread(sendSMS);
        CheckPhoneBalance checkPhoneBalance = new CheckPhoneBalance(accountInfo);
        Thread checkBalance = new Thread(checkPhoneBalance);

        // Each thread have its own information and behaviours defined by java concurrency
        // eg - getId(), getPriority(), getName() etc.. and isAlive(), join(), sleep(), start() etc...
        // eg - smsThread.getId(), smsThread.getPriority(), ...... smsThread.sleep(), smsThread.start() ......

        // ----------------------------------------------------------------------------------
        // ------------------------ Starting and joining threads -----------------------------

        /**
         * join() method allows one thread to wait until another thread completes its execution.
         * (i.e wait until we complete sending SMS and then check balance)
         *
         * in the join() method, the calling thread goes into a waiting state, handing over the control
         * i.e the main thread goes into waiting state, handing over the control to "smsThread"
         */
        smsThread.start();
        smsThread.join(); // main thread hands over the control to sms thread
        // after smsThread completes its task, it returns control to the calling method or main thread here

        checkBalance.start();
        checkBalance.join();
    }

    public static class SendSMS implements Runnable {

        // Initializing AccountInfo
        private AccountInfo accountInfo;
        public SendSMS(AccountInfo accountInfo) {
            this.accountInfo = accountInfo;
        }

        @Override
        public void run() {

            System.out.println("----------- SendSMS ---------------");

            try {
                System.out.println("Sending SMS");
                Thread.sleep(5000);
                accountInfo.decreaseBalance(2); // SMS cost $2
                System.out.println("SMS successfully send to friend Rearc - 9912345678");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static class CheckPhoneBalance implements Runnable {

        // Initializing AccountInfo
        private AccountInfo accountInfo;
        public CheckPhoneBalance(AccountInfo accountInfo) {
            this.accountInfo = accountInfo;
        }

        @Override
        public void run() {

            System.out.println("----------- CheckPhoneBalance ---------------");

            try {
                System.out.println("Retrieving Phone Balance");
                Thread.sleep(500);

                System.out.println("Your remaining balance is: " + accountInfo.getCurrentBalance());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class AccountInfo {
        private String accountHolderName;
        private int currentBalance;

        public AccountInfo(String accountHolderName, int currentBalance) {
            this.accountHolderName = accountHolderName;
            this.currentBalance = currentBalance;
        }

        public int getCurrentBalance(){
            return this.currentBalance;
        }

        public void decreaseBalance(int am){
            this.currentBalance = this.currentBalance - am;
        }
    }

}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Without using join()
----------- SendSMS ---------------
Sending SMS
----------- CheckPhoneBalance ---------------
Retrieving Phone Balance
Your remaining balance is: 200
SMS successfully send to friend Rearc - 9912345678
# Sending SMS took a long, i got the balance check faster 
# But now, we dont know how much SMS cost us.

# Using join
----------- SendSMS ---------------
Sending SMS
SMS successfully send to friend Rearc - 9812345678
----------- CheckPhoneBalance ---------------
Retrieving Phone Balance
Your remaining balance is: 198

[ See: thread methods: isAlive(), join(), wait()-notify() etc… ]

These are the behaviors on the thread instance itself. There are also few Classes that provide us advanced features for thread communication and coordination. We will explore them in the “resource sharing between thread” and “thread communication” section here.

Checkpoint
Okay ! Great. Let’s summarize what we have learned this far. We understood - Thread creation in java, JVM thread processing and execution, Retrieving thread information, thread methods/behaviours, “scheduling thread out” using sleep() methods, interrupting running thread, executing thread one-after-other using join()… and much more concepts you have explored on with [ See ] labels.

It was mostly about concurrency concepts, thread creation and its methods.
Now, let’s bring the “data” on Part 2. Things will get more interesting but also will be a little complicated from now on. But we will get through this together.

Part 2 - Data And Resources Sharing