Before Java 5, the synchronized keyword was used to implement the lock function.
The synchronized keyword can be used as a modifier (synchronized method) or as a statement within a function (synchronized code block).
To master synchronized, the key is to master the use of that thing as a lock. For non-static methods (member methods) of a class, it means to obtain the lock of the object instance; for static methods (class methods) of a class, it is necessary to obtain the lock of the class object; for synchronous code blocks, it is necessary to specify which object's lock is obtained. Synchronized non-static methods can be considered as a synchronized(this) { … } code block containing the entire method.
Whether it is a synchronous code block or a synchronization method, only one thread can enter at a time (at most one thread executes the code segment at the same time.), and if other threads try to enter (whether it is the same synchronous block or a different synchronization block), jvm will hang them (put into the waiting lock pool). This structure is called a critical section in concurrency theory.
In jvm, in order to improve efficiency, each thread running at the same time will have a cache copy of the data it is processing. When we use synchronzied for synchronization, what is really synchronized is the memory block representing the locked object in different threads (the copy data will remain synchronized with the main memory. Now we know why the word synchronization is used). Simply put, after the synchronization block or synchronization method is executed, any modifications made to the locked object must be written back to the main memory before releasing the lock; after entering the synchronization block and obtaining the lock, the data of the locked object is read from the main memory, and the data copy of the thread holding the lock must be synchronized with the data view in the main memory.
The following are specific examples to illustrate the various situations of synchronized.
Synchronized synchronization method
First, let’s take a look at the example of the synchronization method:
public class SynchronizedTest1 extends Thread { private synchronized void testSynchronizedMethod() { for (int i = 0; i < 10; i++) { (().getName() + " testSynchronizedMethod:" + i); try { (100); } catch (InterruptedException e) { (); } } } @Override public void run() { testSynchronizedMethod(); } public static void main(String[] args) { SynchronizedTest1 t = new SynchronizedTest1(); (); (); } }
Running the program outputs:
main testSynchronizedMethod:0 main testSynchronizedMethod:1 main testSynchronizedMethod:2 main testSynchronizedMethod:3 main testSynchronizedMethod:4 main testSynchronizedMethod:5 main testSynchronizedMethod:6 main testSynchronizedMethod:7 main testSynchronizedMethod:8 main testSynchronizedMethod:9 Thread-0 testSynchronizedMethod:0 Thread-0 testSynchronizedMethod:1 Thread-0 testSynchronizedMethod:2 Thread-0 testSynchronizedMethod:3 Thread-0 testSynchronizedMethod:4 Thread-0 testSynchronizedMethod:5 Thread-0 testSynchronizedMethod:6 Thread-0 testSynchronizedMethod:7 Thread-0 testSynchronizedMethod:8 Thread-0 testSynchronizedMethod:9
You can see that the testSynchronizedMethod method is executed synchronously between two threads.
If the main method is modified to the following, the two threads cannot execute synchronously, because the synchronization monitor of the two threads is not the same object and cannot play a synchronous role.
public static void main(String[] args) { Thread t = new SynchronizedTest1(); (); Thread t1 = new SynchronizedTest1(); (); }
The output result is as follows:
Thread-0 testSynchronizedMethod:0 Thread-1 testSynchronizedMethod:0 Thread-0 testSynchronizedMethod:1 Thread-1 testSynchronizedMethod:1 Thread-0 testSynchronizedMethod:2 Thread-1 testSynchronizedMethod:2 Thread-0 testSynchronizedMethod:3 Thread-1 testSynchronizedMethod:3 Thread-0 testSynchronizedMethod:4 Thread-1 testSynchronizedMethod:4 Thread-0 testSynchronizedMethod:5 Thread-1 testSynchronizedMethod:5 Thread-0 testSynchronizedMethod:6 Thread-1 testSynchronizedMethod:6 Thread-0 testSynchronizedMethod:7 Thread-1 testSynchronizedMethod:7 Thread-0 testSynchronizedMethod:8 Thread-1 testSynchronizedMethod:8 Thread-0 testSynchronizedMethod:9 Thread-1 testSynchronizedMethod:9
If the modified main method can be run synchronously between two threads, the testSynchronizedMethod method needs to be declared as a static method, so that the monitors of the two threads are the same object (class object) and can be executed synchronously. The modified code looks like this:
public class SynchronizedTest1 extends Thread { private static synchronized void testSynchronizedMethod() { for (int i = 0; i < 10; i++) { (().getName() + " testSynchronizedMethod:" + i); try { (100); } catch (InterruptedException e) { (); } } } @Override public void run() { testSynchronizedMethod(); } public static void main(String[] args) { Thread t = new SynchronizedTest1(); (); Thread t1 = new SynchronizedTest1(); (); } }
The output result is as follows:
Thread-0 testSynchronizedMethod:0 Thread-0 testSynchronizedMethod:1 Thread-0 testSynchronizedMethod:2 Thread-0 testSynchronizedMethod:3 Thread-0 testSynchronizedMethod:4 Thread-0 testSynchronizedMethod:5 Thread-0 testSynchronizedMethod:6 Thread-0 testSynchronizedMethod:7 Thread-0 testSynchronizedMethod:8 Thread-0 testSynchronizedMethod:9 Thread-1 testSynchronizedMethod:0 Thread-1 testSynchronizedMethod:1 Thread-1 testSynchronizedMethod:2 Thread-1 testSynchronizedMethod:3 Thread-1 testSynchronizedMethod:4 Thread-1 testSynchronizedMethod:5 Thread-1 testSynchronizedMethod:6 Thread-1 testSynchronizedMethod:7 Thread-1 testSynchronizedMethod:8 Thread-1 testSynchronizedMethod:9
The situation of synchronous blocks is similar to the synchronization method, except that the synchronous block reduces the granularity of synchronization control, which can better exert the efficiency of multi-threaded parallel execution.
Use this object to control synchronization between the same object instances:
public class SynchronizedTest2 extends Thread { private void testSynchronizedBlock() { synchronized (this) { for (int i = 0; i < 10; i++) { (().getName() + " testSynchronizedBlock:" + i); try { (100); } catch (InterruptedException e) { (); } } } } @Override public void run() { testSynchronizedBlock(); } public static void main(String[] args) { SynchronizedTest2 t = new SynchronizedTest2(); (); (); } }
Output result:
main testSynchronizedBlock:0 main testSynchronizedBlock:1 main testSynchronizedBlock:2 main testSynchronizedBlock:3 main testSynchronizedBlock:4 main testSynchronizedBlock:5 main testSynchronizedBlock:6 main testSynchronizedBlock:7 main testSynchronizedBlock:8 main testSynchronizedBlock:9 Thread-0 testSynchronizedBlock:0 Thread-0 testSynchronizedBlock:1 Thread-0 testSynchronizedBlock:2 Thread-0 testSynchronizedBlock:3 Thread-0 testSynchronizedBlock:4 Thread-0 testSynchronizedBlock:5 Thread-0 testSynchronizedBlock:6 Thread-0 testSynchronizedBlock:7 Thread-0 testSynchronizedBlock:8 Thread-0 testSynchronizedBlock:9
Use class objects to control synchronization between different instances:
public class SynchronizedTest2 extends Thread { private void testSynchronizedBlock() { synchronized () { for (int i = 0; i < 10; i++) { (().getName() + " testSynchronizedBlock:" + i); try { (100); } catch (InterruptedException e) { (); } } } } @Override public void run() { testSynchronizedBlock(); } public static void main(String[] args) { Thread t = new SynchronizedTest2(); (); Thread t2 = new SynchronizedTest2(); (); } }
Output result:
Thread-0 testSynchronizedBlock:0 Thread-0 testSynchronizedBlock:1 Thread-0 testSynchronizedBlock:2 Thread-0 testSynchronizedBlock:3 Thread-0 testSynchronizedBlock:4 Thread-0 testSynchronizedBlock:5 Thread-0 testSynchronizedBlock:6 Thread-0 testSynchronizedBlock:7 Thread-0 testSynchronizedBlock:8 Thread-0 testSynchronizedBlock:9 Thread-1 testSynchronizedBlock:0 Thread-1 testSynchronizedBlock:1 Thread-1 testSynchronizedBlock:2 Thread-1 testSynchronizedBlock:3 Thread-1 testSynchronizedBlock:4 Thread-1 testSynchronizedBlock:5 Thread-1 testSynchronizedBlock:6 Thread-1 testSynchronizedBlock:7 Thread-1 testSynchronizedBlock:8 Thread-1 testSynchronizedBlock:9
When using the synchronized keyword for synchronization control, you must grasp the object monitor. Only the process that obtains the monitor can run, and everything else needs to be waited to obtain the monitor. Any non-null object can be used as an object monitor. When synchronized acts on a method, the object instance is locked; when acting on a static method, the object instance is locked corresponding to the object.
Synchronous method for two threads to access an object at the same time
When two concurrent threads access the synchronous method of the same object, only one thread can be executed. Another thread must wait for the current thread to execute this before it can be executed.
public class TwoThread { public static void main(String[] args) { final TwoThread twoThread = new TwoThread(); Thread t1 = new Thread(new Runnable() { public void run() { (); } }, "A"); Thread t2 = new Thread(new Runnable() { public void run() { (); } }, "B"); (); (); } public synchronized void syncMethod() { for (int i = 0; i < 5; i++) { (().getName() + " : " + i); try { (500); } catch (InterruptedException ie) { } } } }
Output result:
A : 0 A : 1 A : 2 A : 3 A : 4 B : 0 B : 1 B : 2 B : 3 B : 4
The synchronization method of two objects is accessed by two threads
In this case, synchronized does not work, just like the ordinary method. Because the corresponding locks are their respective objects.
public class TwoObject { public static void main(String[] args) { final TwoObject object1 = new TwoObject(); Thread t1 = new Thread(new Runnable() { public void run() { (); } }, "Object1"); (); final TwoObject object2 = new TwoObject(); Thread t2 = new Thread(new Runnable() { public void run() { (); } }, "Object2"); (); } public synchronized void syncMethod() { for (int i = 0; i < 5; i++) { (().getName() + " : " + i); try { (500); } catch (InterruptedException ie) { } } } }
One of the possible outputs:
Object2 : 0 Object1 : 0 Object1 : 1 Object2 : 1 Object2 : 2 Object1 : 2 Object2 : 3 Object1 : 3 Object1 : 4 Object2 : 4
The two threads access the synchronized static method
In this case, since Class is locked, at any time, only one thread can execute the static method.
Accessing synchronization method and asynchronous method simultaneously
When one thread accesses one synchronization method of an object, another thread can still access the asynchronous method in that object.
public class SyncAndNoSync { public static void main(String[] args) { final SyncAndNoSync syncAndNoSync = new SyncAndNoSync(); Thread t1 = new Thread(new Runnable() { public void run() { (); } }, "A"); (); Thread t2 = new Thread(new Runnable() { public void run() { (); } }, "B"); (); } public synchronized void syncMethod() { for (int i = 0; i < 5; i++) { (().getName() + " at syncMethod(): " + i); try { (500); } catch (InterruptedException ie) { } } } public void noSyncMethod() { for (int i = 0; i < 5; i++) { (().getName() + " at noSyncMethod(): " + i); try { (500); } catch (InterruptedException ie) { } } } }
One possible output:
B at noSyncMethod(): 0 A at syncMethod(): 0 B at noSyncMethod(): 1 A at syncMethod(): 1 B at noSyncMethod(): 2 A at syncMethod(): 2 B at noSyncMethod(): 3 A at syncMethod(): 3 A at syncMethod(): 4 B at noSyncMethod(): 4
Different synchronization methods for accessing the same object
When a thread accesses synchronization method A of an object, other threads access to all other synchronization methods in the object will be blocked. Because the first thread has obtained the object lock and other threads cannot obtain the lock, although it is accessing a different method, it does not obtain the lock and cannot access it.
public class TwoSyncMethod { public static void main(String[] args) { final TwoSyncMethod twoSyncMethod = new TwoSyncMethod(); Thread t1 = new Thread(new Runnable() { public void run() { twoSyncMethod.syncMethod1(); } }, "A"); (); Thread t2 = new Thread(new Runnable() { public void run() { twoSyncMethod.syncMethod2(); } }, "B"); (); } public synchronized void syncMethod1() { for (int i = 0; i < 5; i++) { (().getName() + " at syncMethod1(): " + i); try { (500); } catch (InterruptedException ie) { } } } public synchronized void syncMethod2() { for (int i = 0; i < 5; i++) { (().getName() + " at syncMethod2(): " + i); try { (500); } catch (InterruptedException ie) { } } } }
Output result:
A at syncMethod1(): 0 A at syncMethod1(): 1 A at syncMethod1(): 2 A at syncMethod1(): 3 A at syncMethod1(): 4 B at syncMethod2(): 0 B at syncMethod2(): 1 B at syncMethod2(): 2 B at syncMethod2(): 3 B at syncMethod2(): 4