The synchronized keyword does not lock the function, but the object, meaning that two threads cannot use the same object concurrently. synchronized void function2 is just syntactic sugar for
void function2(){synchronized(this){//
The reason to synchronize, i.e., lock objects is that no thread can see an object in a state where its invariant is broken. In your example, the class Example has no state, and hence no invariant, meaning that you do not need to lock it.
What you seem to be concerned about are the local variables of function2. However, local variables are never shared between threads, so each thread will have its own instance of each local variable.
Addendum: As suggested by user hexafraction, an example where synchronization is required:
Consider the following simple class:
public class Example { public int count = 0; public void increment() { count++; } public void decrement() { count--; } }
This class is mutable; its state is defined by the value of count. If a client calls either increment or decrement, then the state is supposed to change. Both methods have a contract to adhere to:
increment must guarantee that the value of count is the old value of count plus one. Let's denote this contract by count = old(count) + 1
Simliarly, the contract of decrement is count = old(count) - 1
Let's run this class sequentally:
public static void main(String[] args) { Example sharedData = new Example(); for (int i = 0; i < 1000; i++) sharedData.increment(); System.out.println("Incrementer finished"); for (int i = 0; i < 1000; i++) sharedData.decrement(); System.out.println("Decrementer finished"); System.out.println(sharedData.count); }
It prints:
Incrementer finished Decrementer finished 0
We can run the code as much as we want, the result will always be the same.
Let us define multiple threads that use the same instance of the class Example concurrently:
public static void main(String[] args) throws InterruptedException { Example sharedData = new Example(); Thread incrementer = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) sharedData.increment(); System.out.println("Incrementer finished"); } }); Thread decrementer = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) sharedData.decrement(); System.out.println("Decrementer finished"); } }); incrementer.start(); decrementer.start(); incrementer.join(); decrementer.join(); System.out.println(sharedData.count); }
We now have two threads: An incrementer and a decrementer. The code looks a bit different, but we might expect it to reach the same result. Again, we call increment and decrement both 1000 times on our shared sharedData. But now, the result is completely nondeterministic. Running the code multiple times, the numbers that are printed may be: 16, -76, 138, -4.
How can this be? We are always either adding one or subracting one, but after doing both 1000 times, we ought to have the value 0, right? The problem is that one thread may be ignorant to changes of the other thread. Note that count++ does not happen atomic; it is the same as count = count + 1, which consists of a read, a computation and a write.
Consider the following sequential history:
incrementer enters increment and reads the value of count, count == 0 decrementer enters decrement and reads the value of count, count == 0 incrementer adds one and modifies the state, count == 1 decrementer subtracts one and modifies the state, count == -1
Note that the state change computed by decrementer is based on the value of count that it read, i.e., 0, which means that it did not see the state changes done by the incrementer.
There are multiple ways to solve this problem, but let us try the synchronized keyword. We can disallow concurrent modifications of the shared instance of Example by locking the instance. So lets modify our class:
public class Example { public int count = 0; public synchronized void increment() { count++; } public synchronized void decrement() { count--; } }
It is important to have both methods lock the instance, because both methods must not see the object in an inconsistent state.
What if we could not modify the code of Example, e.g., because it is part of a library we use? How could we employ the synchronized keyword to use the code by multiple threads? As already mentioned, synchronized void increment(){ is the same as void increment(){synchronized(this), so synchronization is not an attribute of the method, but the object. Leaving the code of Example unchanged, we could have changed our client instead:
public static void main(String[] args) throws InterruptedException { Example sharedData = new Example(); Thread incrementer = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) synchronized (sharedData){ sharedData.increment(); } System.out.println("Incrementer finished"); } }); Thread decrementer = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) synchronized (sharedData){ sharedData.decrement(); } System.out.println("Decrementer finished"); } }); incrementer.start(); decrementer.start(); incrementer.join(); decrementer.join(); System.out.println(sharedData.count); }
function2.