Why is synchronization required in Java



The synchronized keyword

As is well known, there are resources that have to be allocated exclusively, including output on the printer and write access to files. Java provides the keyword so that a thread can process a task exclusively synchronized ready. When a thread enters an area of ​​code that begins with synchronized is protected, he can process this area exclusively. No other thread can then enter this area. Each Java object has a monitor for this purpose. This monitoring instance contains per object exactly one Lock. A thread wants one with synchronized enter the protected area, he must request a lock from the associated object. If he is already taken, he has to wait. Since there is only one lock, a thread that has this lock inevitably locks all the others synchronized protected areas of this property. It may also block areas that have nothing to do with the code just processed by the thread. Loss of performance is the logical consequence of this brutal behavior, so to speak.


The use of synchronized

With synchronized you can protect a specific area of ​​code or an entire method. In the first case one speaks of a synchronized block.

synchronized (anObject) {// statements}

This area is now protected by the specified object, which means that "anObject" grants the lock. Often this object is this because the block is in a non-static method of the "anObject" object.

public synchronized void method () {// statements}

Here the entire method is assigned exclusively. Of course, any type of return is possible, as well as other protection states. The above notation is just an abbreviation for the following:

public void method () {synchronized (this) {// statements}}

Static synchronized blocks and static synchronized methods

You can also work with synchronized on the static level. Since a synchronized block requires an object, the class object assigned to the class is used in this case.


The static synchronized block
synchronized (MyK KlassenName.class) {// statements}

This area is now protected by the specified class object.


The static synchronized method
public static synchronized void method () {// statements}

As you would expect, the notation above is equivalent to the following.

public static void method () {synchronized (MyClassName.class) {// statements}}

The name of the class is of course the one in which the method is located.

The following example shows a static method that includes both a protected area and an unprotected area. Both areas output 10 characters to the console. There is also a thread class that works with this method. The thread objects can be passed which strings they should output in which area. So that the two areas do not run too quickly, they are slowed down by sleep. The first thread outputs "1" in the unprotected area and "A" in the protected area, the second thread in the unprotected area "2" and in the protected area "B".

The main class with the static method print

public class SynchronizedDemo {public static void main (String [] args) {Thread th1 = new OutputThread ("1", "A"); Thread th2 = new outputthread ("2", "B"); th1.start (); th2.start (); } public static void print (String s1, String s2) {for (int i = 0; i <10; i ++) {System.out.print (s1); try {thread.sleep (200); } catch (InterruptedException ex) {}} synchronized (SynchronizedDemo.class) {for (int i = 0; i <10; i ++) {System.out.print (s2); try {thread.sleep (200); } catch (InterruptedException ex) {}}}}}

The thread class

public class outputthread extends thread {private string s1; private string s2; public output thread (string s1, string s2) {this.s1 = s1; this.s2 = s2; } public void run () {SynchronizedDemo.print (s1, s2); }}

The output is not completely determined. The A's and the B's will appear in blocks at the end, everything else cannot be predicted. Here two runs of the program.

12212121212121122112BBBBBBBBBBAAAAAAAAAA 12121212212112121212AAAAAAAAAABBBBBBBBBB

Here, of course, the question arises: What happens to the lock while the thread is sleeping?. Since the sleep () phases also exist in the synchronized area, the lock is not returned. Otherwise the second thread would access it and the issues would mix. The following table clarifies this for all thread-relevant methods.


The behavior of join (), notify (), sleep (), wait () and yield () in relation to locks

It is important to know which methods return a lock or not when called.

Lock behavior of the thread-relevant methods
methodclassLure behavior
join ()ThreadKeeps the lock
sleep ()ThreadKeeps the lock
yield ()ThreadKeeps the lock
notify ()ObjectKeeps the lock
wait ()ObjectReturns the lock

Remember rule: only wait () returns the lock.


Locks assigned with synchronized are reentrant (re-entrant)

What happens if a thread has a lock and wants to enter another area that is protected with the same lock. He'll request a lock, but he can't get one anymore. Can the monitor remember who it has given the lock to? Can he say you already have the lock, use it. We consider the following situation.

public class SynchronizeIsReentrant {public synchronized first () {// do something second (); // do something second (); // do something} public synchronized second () {// do something}}

When entering the second () method, the executing thread requests a lock from the monitor again. Now there are two ways in which the monitor can react. To do this, let's imagine the monitor as a speaking being.

First option:

Monitor says: I no longer have a lock (that's it).

Second option:

Monitor says: You already have the lock, it also applies to the new situation.

In the first situation one speaks of one non-reentranten Lock, the second situation is called as expected reentrant. A reentranter lock is something like a master key, it locks everywhere. If synchronized were not implemented as reentrant, there would be a deadlock because the monitor only has one lock.


A thread locks all synchronized methods

If a thread T1 receives access to a synchronized method m1, for example, it receives the lock and can thus process the method exclusively. But because there is only one lock per object, it inevitably prevents access to all other synchronized methods. As a result, no thread has access to any other synchronized method as long as T1 is busy with m1. That was one of the reasons for the new development of the Vector class, in which numerous methods are synchronized and which is therefore anything but high-performance. "Synchronized" was therefore not used in the ArrayList class. There is a static method around a list in the Collections utility class
to be synchronized afterwards: List list = Collections.synchronizedList (new ArrayList ());

The following example demonstrates this behavior

A MethodMagazine class has three identically structured synchronized methods. These report with a time stamp when they are called. Each method takes 5 seconds, which is achieved with a sleep () call, because sleep () does not return the lock. Three threads receive a single object of this class. Each thread calls exactly one of the three methods, but different. The start calls come one after the other. Regardless of which thread can start, it blocks access to the other two methods for 5 seconds. Since each method begins with an output of the time stamp, you can see exactly how the remaining methods are blocked.

The MethodMagazine class

class MethodMagazine {public synchronized void syncMethod_1 () {System.out.println ("syncMethod_1 ()" + new Timestamp (System.currentTimeMillis ())); try {TimeUnit.SECONDS.sleep (5);} catch (InterruptedException ex) {ex.printStackTrace (); }} public synchronized void syncMethod_2 () {System.out.println ("syncMethod_2 ()" + new Timestamp (System.currentTimeMillis ())); try {TimeUnit.SECONDS.sleep (5);} catch (InterruptedException ex) {ex.printStackTrace (); }} public synchronized void syncMethod_3 () {System.out.println ("syncMethod_3 ()" + new Timestamp (System.currentTimeMillis ())); try {TimeUnit.SECONDS.sleep (5);} catch (InterruptedException ex) {ex.printStackTrace (); }}}

The three threads

class Task1 implements Runnable {private MethodMagazine mm = null; public Task1 (MethodMagazine mm) {this.mm = mm; } public void run () {mm.syncMethod_1 (); }} class Task2 implements Runnable {private MethodMagazine mm = null; public Task2 (MethodMagazine mm) {this.mm = mm; } public void run () {mm.syncMethod_2 (); }} class Task3 implements Runnable {private MethodMagazine mm = null; public Task3 (MethodMagazine mm) {this.mm = mm; } public void run () {mm.syncMethod_3 (); }}

The main class

public class SynchronizedDemo {public static void main (String [] args) {System.out.println ("main"); MethodMagazine mm = new MethodMagazine (); Thread th1 = new Thread (new Task1 (mm)); Thread th2 = new Thread (new Task2 (mm)); Thread th3 = new Thread (new Task3 (mm)); th1.start (); th2.start (); th3.start (); } System.out.println ("end main"); } // end main} // end class

Some processes

main end main syncMethod_1 () 2020-03-11 10: 06: 11.055 syncMethod_3 () 2020-03-11 10: 06: 16.064 syncMethod_2 () 2020-03-11 10: 06: 21.065 main end main syncMethod_1 () 2020- 03-11 10: 07: 04.475 syncMethod_3 () 2020-03-11 10: 07: 09.484 syncMethod_2 () 2020-03-11 10: 07: 14.485 main end main syncMethod_1 () 2020-03-11 10: 07: 33.726 syncMethod_2 () 2020-03-11 10: 07: 38.737 syncMethod_3 () 2020-03-11 10: 07: 43.737

One possible way out: lure with different objects

We no longer work with synchronized methods, but with synchronized blocks in the methods. This allows us to use different objects to lure. The objects are injected via the constructor of the task classes. In the following example, two Tsk classes are given the same object, but the third task is given a different one.

The new class MethodMagazine

class MethodMagazine {public void syncMethod_1 (Object ob) {synchronized (ob) {System.out.println ("syncMethod_1 () begin" + new Timestamp (System.currentTimeMillis ())); try {TimeUnit.SECONDS.sleep (5);} catch (InterruptedException ex) {ex.printStackTrace (); } System.out.println ("syncMethod_1 () end" + new Timestamp (System.currentTimeMillis ())); }} public void syncMethod_2 (Object ob) {synchronized (ob) {System.out.println ("syncMethod_2 () begin" + new Timestamp (System.currentTimeMillis ())); try {TimeUnit.SECONDS.sleep (5);} catch (InterruptedException ex) {ex.printStackTrace (); } System.out.println ("syncMethod_2 () end" + new Timestamp (System.currentTimeMillis ())); }} public void syncMethod_3 (Object ob) {synchronized (ob) {System.out.println ("syncMethod_3 () begin" + new Timestamp (System.currentTimeMillis ())); try {TimeUnit.SECONDS.sleep (5);} catch (InterruptedException ex) {ex.printStackTrace (); } System.out.println ("syncMethod_3 () end" + new Timestamp (System.currentTimeMillis ())); }}}

The three threads that now receive an object via the constructor

class Task1 implements Runnable {private MethodMagazine mm = null; private object monitor; public Task1 (MethodMagazine mm, Object monitor) {this.mm = mm; this.monitor = monitor; } public void run () {mm.syncMethod_1 (monitor); }} class Task2 implements Runnable {private MethodMagazine mm = null; private object monitor; public Task2 (MethodMagazine mm, Object monitor) {this.mm = mm; this.monitor = monitor; } public void run () {mm.syncMethod_2 (monitor); }} class Task3 implements Runnable {private MethodMagazine mm = null; private object monitor; public Task3 (MethodMagazine mm, Object monitor) {this.mm = mm; this.monitor = monitor; } public void run () {mm.syncMethod_3 (monitor); }}

The main class

/ ** * A lock locks everything! * but here: different objects for different methods * / public class SynchronizedDemo {public static void main (String [] args) {System.out.println ("main"); MethodMagazine mm = new MethodMagazine (); Object monitor1 = new Object (); Object monitor2 = new Object (); Object monitor3 = monitor1; Thread th1 = new Thread (new Task1 (mm, monitor1)); Thread th2 = new Thread (new Task2 (mm, monitor2)); Thread th3 = new Thread (new Task3 (mm, monitor3)); th1.start (); th2.start (); th3.start (); System.out.println ("end main"); } // end main} // end class

Some processes

main end main syncMethod_1 () begin 2020-03-11 12: 19: 41.616 syncMethod_2 () begin 2020-03-11 12: 19: 41.616 syncMethod_1 () end 2020-03-11 12: 19: 46.625 syncMethod_2 () end 2020 -03-11 12: 19: 46.625 syncMethod_3 () begin 2020-03-11 12: 19: 46.625 syncMethod_3 () end 2020-03-11 12: 19: 51.626 main end main syncMethod_2 () begin 2020-03-11 12 : 20: 30.581 syncMethod_1 () begin 2020-03-11 12: 20: 30.581 syncMethod_1 () end 2020-03-11 12: 20: 35.591 syncMethod_2 () end 2020-03-11 12: 20: 35.591 syncMethod_3 () begin 2020-03-11 12: 20: 35.591 syncMethod_3 () end 2020-03-11 12: 20: 40.592 main end main syncMethod_1 () begin 2020-03-11 12: 21: 18.396 syncMethod_2 () begin 2020-03-11 12: 21: 18.396 syncMethod_2 () end 2020-03-11 12: 21: 23.405 syncMethod_1 () end 2020-03-11 12: 21: 23.405 syncMethod_3 () begin 2020-03-11 12: 21: 23.405 syncMethod_3 () end 2020-03-11 12: 21: 28.406 main end main syncMethod_1 () begin 2020-03-11 12: 22: 01.768 syncMethod_2 () begin 2020-03-11 12: 22: 01.768 syncMethod_1 () end 2020-03- 11 12:22:06. 777 syncMethod_3 () begin 2020-03-11 12: 22: 06.777 syncMethod_2 () end 2020-03-11 12: 22: 06.778 syncMethod_3 () end 2020-03-11 12: 22: 11.778

This concept was expanded in Java 5 by introducing a separate class Lock instead of the Object class, which has additional options for configuring a lock.