Networking - Java Threads and Synchronization

    Hello my friends it's me again drifter! Today we will talk about Threads and Synchronization in Java. I will start off with 2 ways that we can create a Thread, then get into how we synchronize and lastly get into an example! So, without further do let's get started!


Threads in Java:

    A Thread has a specific lifecycle that you can see at the beginning of this post. The first implementation is based on the interface Runnable that makes a run() method be called after creating and starting a Thread. The second implementation is based on extending the class Thread and again starting the Thread after creating it!

So, a Thread using Runnable looks like this:

class RunnableDemo implements Runnable{
    private Thread t;
    private String threadName;
    RunnableDemo(String name) {
 threadName = name;
    }
    public void run() {
        // thread code 
    }
    public void start() {
 if (t == null) {
     t = new Thread(this, threadName);
     t.start();
 }
    }
}


    To use such a Thread we then have to create a RunnableDemo Object and call the start method of it like that:

RunnableDemo r1 = new RunnableDemo("MyThread");
r1.start();


A Thread created extending the Thread class looks like this:

class ThreadDemo extends Thread{
    private Thread t;
    private String threadName;
     ThreadDemo(String name) {
 threadName = name;
    }
    public void run() {
         // thread code
    }
    public void start() {
 if (t == null) {
     t = new Thread(this, threadName);
     t.start();
 }
    }
}


    You can see that the Code is exactly the same except that we now use the Thread object. So, we can call the Thread in the exact same way as before:

ThreadDemo t1 = new ThreadDemo("MyThread");
t1.start();


     Another way of running Threads with the Runnable Implementation is using an Executer Service! We will use this in our Example after we talk about Synchronization.

Synchronization in Java:

    To synchronize something we use the synchronized() statement. Java in the background uses monitors to let only one Thread get into the synchronized part! 

So, to synchronize a block of statements we use:

synchronized(){
    // statements
}


    This mostly makes sense to be used directly on a function that gets something from a shared source (Producer-Consumer problem), writes something into a shared source (Readers-Writers problem) etc.

So, a synchronized function looks like this:

public synchronized DataType FunctioName(//parameters){
    // synchronized statements
}


    And this is actually all that you need to synchronize in Java! So, let's now get into an Example for the Producer-Consumer problem!

Producer-Consumer Problem:

    Suppose we have a common shared memory that is a Stack and the Producers will put random numbers inside and one Consumer will calculate the sum of all of them! I will have 1 Consumer count the sum of 10 numbers and create 5 Producers that will put 2 random values in the Stack. To make it easier to synchronize we will create a SharedMemory Objec that will only get synchronized in the put() function. This Code makes not so much sense to be synchronized, but I guess that it will help you understand how Threads and Synchronization in Java work.

So, here are the Classes:

Consumer:

public class Consumer implements Runnable {
    SharedLocation shl;
    Consumer(SharedLocation shl) {
 this.shl = shl;
    }
    @Override
    public void run() {
 int sum = 0;
 int val;
 for (int i = 0; i < 10; i++) {
     if (shl.notEmpty()){
     val = shl.get();
     if (val != -1) {
         sum += val;
     }
            }
            else{
     i--;
     }
     try {
     Thread.sleep(10);
     } catch (InterruptedException e) {
     System.out.println(e.getMessage());
     }
 }
 System.out.println("Sum is " + sum);
    }
}


Producer:

public class Producer implements Runnable {
    SharedLocation shl;
    Producer(SharedLocation shl) {
 this.shl = shl;
    }
    @Override
    public void run() {
 int val;
 for (int i = 0; i < 2; i++) {
     val = 1 + (int) (Math.random() * 100);
     shl.put(val);
     System.out.println("Put val " + val);
     try {
         Thread.sleep(10);
     } catch (InterruptedException e) {
         System.out.println(e.getMessage());
     }
 }

    }
}


SharedLocation:

import java.util.Stack;
public class SharedLocation {
    Stack<Integer> s = new Stack<Integer>();
    public int get() {
 if (notEmpty()) {
     return s.pop();
 }
 return -1;
    }
    public synchronized void put(int val) {
 s.push(val);
    }
    public boolean notEmpty() {
 return !s.isEmpty();
    }
}


Main:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
    public static void main(String args[]) {
 ExecutorService application = Executors.newCachedThreadPool();
 SharedLocation shl = new SharedLocation();
 for (int i = 0; i < 5; i++) {
     application.execute(new Producer(shl));
 }
 application.execute(new Consumer(shl));
    }
}


    You can see that using the Executor Service we simply execute() the specific Threads and don't need a start() method anymore! I will not explain much more and I hope you can understand the Code...

An Execution could look like this:

Put val 55

Put val 31

Put val 47

Put val 10

Put val 95

Put val 24

Put val 2

Put val 81

Put val 84

Put val 7

Sum is 436


And this is actually it! Hope you enjoyed it!

Bye!

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now