Synchronization in Java:
In mulch-threaded programming we must consider synchronization of commonly shared resources/operations, to improve performance and avoid weird behavior of programming to get expected result. Why it is important means, if a common resource is used by more than one thread, then there is possibility of changing of state/values of commonly shared resources when another thread also working on the same. So it will lead to unexpected behavior.
Let us see the example program which counts the numbers from 1-3000 and prints them. But it is called by two persons(threads) to count. We have two methods one is synchronized and another one is non-synchronized normal method.
Note : Difference between synchronizing method and block in a method.
1. If you are overriding a method which is not synchronized in its declaration then you cover the code part inside the method by using synchronized keyword so the part of the code becomes synchronized.
2. If you synchronize method it is not accessible for other till the thread release control, but if you have synchronized block then the others will get access to the method and only wait to execute particular lines of code only. This will improve performance of the whole system because many objects can acquire the system.
Output of Normal method : (public void incrementAndPrintInt) is shown below when two persons Thread_1 and Thread_2 called them.
Thread_1 0
Thread_2 0
Thread_1 1
Thread_2 2
Thread_2 4
Thread_2 5
Thread_2 6
Thread_1 3
Thread_2 7
Thread_1 8
Thread_2 9
Thread_1 10
Output of Synchronized method : (public synchronized void incrementAndPrintInt) is shown below when two persons Thread_1 and Thread_2 called them.
Thread_2 0
Thread_2 1
Thread_2 2
Thread_2 3
Thread_2 4
Thread_2 5
Thread_2 6
Thread_2 7
Thread_2 8
Thread_2 9
Thread_2 10
Explanation:
In the normal method it is accessed by both the persons and called so the count values is affected by both of them, ie., the values increased by Thread_1 is unknown to Thread_2. So Thread_1 missed to count 2,4,5,6,7. And the counting also not in order. This is a unwanted/unexpected behavior of the Counter.
But if you see the output of the Synchronized method then count is in order like 1,2,3,4....10. And Thread_1 is not interrupted the method while Thread_2 is counting. This is what we want. When one person is working with one resource don't let the others disturb. So here the Thread_2 locked the method and releases if after completing its job to another thread. During the lock period no one disturbs the counter.
Code 1 : Main Class instantiates counter and shares it to threas objects and triggers them
public class SynchronizedTest {
public static void main(String[] args) {
MyCounter c = new MyCounter();
Thread1 t1 = new Thread1(c);
Thread2 t2 = new Thread2(c);
t1.start();
t2.start();
}
}
Code 2: MyCounter class has method incrementAndPrintInt is given here as non-synchronized. But to see the output of synchronized please add synchronized keyword in the method signature and compare the difference.
public class MyCounter {
int count =0;
// put synchronized before void for output two
public void incrementAndPrintInt(String s) {
while (count < 1000) {
System.out.println(s+" "+count);
count++;
}
}
}
Code 3: Thread1 is a class calls the counter count method.
public class Thread1 extends Thread {
MyCounter counter;
Thread1(MyCounter counter) {
this.counter = counter;
}
public void run() {
counter.incrementAndPrintInt("Thread_1");
}
}
Code 4: Thread2 is a class calls the counter count method.
public class Thread2 extends Thread {
MyCounter counter;
Thread2(MyCounter counter) {
this.counter = counter;
}
public void run() {
counter.incrementAndPrintInt("Thread_2");
}
}
Simple Thread Sample using Runnable interface Implementation
class 1: This MyPrinter is class, has method print() which will be called from multiple threads and is shared across multiple threads.
public class MyPrinter {
public void print(String pName,int i)
{
System.out.println(pName+"_"+i);
}
}
class 2: This is the class which implements Runnable interface and overrides the run methods, the job/task you want to perform in thread should be put inside the methods run().
public class Runnable1 implements Runnable {
private MyPrinter printer;
Runnable1(MyPrinter printer) {
this.printer = printer;
}
public void run() {
int i = 0;
while (i < 200) {
printer.print("Runnable_1", i);
i++;
}
}
}
class 2: This is the another same as above class which implements Runnable interface and
overrides the run methods, the another job/task you want to perform in thread
should be put inside the methods run().
public class Runnable2 implements Runnable {
private MyPrinter printer;
Runnable2(MyPrinter printer) {
this.printer = printer;
}
public void run() {
int i = 0;
while (i < 200) {
printer.print("Runnable_2", i);
i++;
}
}
}
class 4: The main class to be executed is described here. Please see the difference here, unlike instantiating the Runnable and calling start() method on it, we instantiate Runnable1 and Runnable2 objects and passing those instance as argument to another Thread objects. So these are the real objects.
public class SimpleThreadTest {
public static void main(String[] args) {
MyPrinter p = new MyPrinter();
Runnable1 t1= new Runnable1(p);
Runnable2 t2= new Runnable2(p);
new Thread(t1).start();
new Thread(t2).start();
}
}