Principle of synchronized
In Java, each object has only one synchronization lock. This also means that the synchronization lock is dependent on the existance of object.
When we call the synchronized method of an object, we acquire the synchronization lock of the object. For example, synchronized(obj) acquires the synchronization lock of “obj”.
The access from different threads to synchronization locks are mutually exclusive. In other words, at a certain point in time, the synchronization lock of an object can only be acquired by one thread! With synchronization locks, we can achieve mutually exclusive access to “objects/methods” in multiple threads environment. For example, there are now two threads A and B, both of which will access the “synchronous lock of object obj”. Suppose, at a certain moment, thread A acquires “obj’s synchronization lock” and is performing some operations; and at this time, thread B also attempts to acquire “obj’s synchronization lock” - thread B will fail to acquire it and must wait, Until thread A releases the “synchronization lock of the obj”, thread B cannot acquire the “synchronization lock of obj”.
Basic rules of synchronized
When a thread accesses the “synchronized method” or “synchronized code block” of “some object”, other threads’ access to the “synchronized method” or “synchronized code block” of “this object” will be blocked
Example:
1 | class MyRunable implements Runnable { |
Results:
1 | t1 loop 0 |
The synchronized (this) exists in the run() method, and both t1 and t2 are threads created based on demo. We can think of this in synchronized(this) as the demo. Threads t1 and t2 share the “demo synchronization lock”. Therefore, when one thread is running, the other thread must wait for the “running thread” to release the “demo synchronization lock” before running.
Then let’s modify the above code, and then run to see how is the results:
1 | class MyThread extends Thread { |
This time MyThread class directly inherits from Thread, and both t1 and t2 are MyThread child threads.
Results:
1 | t1 loop 0 |
This in synchronized(this) refers to the “current class object”, that is, the current object corresponding to the class where synchronized(this) is located. Its role is to acquire the “synchronization lock of the current object”.
For Demo1_2, this in synchronized(this) represents the MyThread object, and t1 and t2 are two different MyThread objects, so when t1 and t2 execute synchronized(this), they acquire synchronization locks of different objects. For Demo1_1, this in synchronized(this) represents the MyRunable object; t1 and t2 share a MyRunable object. Therefore, one thread acquires the synchronization lock of the object and causes another thread to wait.
When a thread accesses the “synchronized method” or “synchronized code block” of “some object”, other threads can still access the “non synchronized code block” of “this object”.
1 | class Count { |
Results:
1 | t1 synMethod loop 0 |
Main thread creates t1 and t2. t1 will call the synMethod() method of the count object, which contains the synchronization block; and t2 will call the nonSynMethod() method of the count object, which is not a synchronization method. While t1 is running, although synchronized(this) is called to obtain the count synchronization lock. It does not cause t2 to block because t2 does not use the “count synchronization lock.
When a thread accesses the “synchronized method” or “synchronized code block” of “an object”, other threads’ access to other “synchronized methods” or “synchronized code blocks” of the “object” will be blocked.
We modify the nonSynMethod() method in the above example with synchronized (this)
1 | class Count { |
Results:
1 | t1 synMethod loop 0 |
Main thread creates t1 and t2. synchronized(this) is called when both t1 and t2 run, this is the count object, and t1 and t2 share the count. Therefore, when t1 is running, t2 will be blocked and wait for t1 to run and release the count synchronization lock.
Synchronized method and Synchronized block
Synchronized method is to modify the method with synchronized
1 | public synchronized void foo1() { |
Synchronized code block is to modify the code block with synchronized.
1 | public void foo2() { |
this in the synchronized code block refers to the current object. You can also replace this with other objects, for example, replace this with obj, then foo2() acquires the synchronization lock of obj when executing synchronized(obj).
Synchronized code blocks can more precisely control conflicts and restrict access to areas, and sometimes perform more efficiently.
Instance lock and global lock
Instance loc
- lock on an instance object. If the class is a singleton, then the lock also has the concept of a global lock.
- The instance lock corresponds to the
synchronizedkeyword.
Global lock
- The lock is for a class, no matter how many objects are instantiated, the thread shares the lock.
- The global lock corresponds to
static synchronized(or locked on the class or classloader object of the class)
Let’s look at the following example:
1 | public class Something { |
Suppose, Something has two instances x and y. Analyze the locks acquired by the following 4 scenarios.
x.isSyncA()andx.isSyncB()x.isSyncA()andy.isSyncA()x.cSyncA()andy.cSyncB()x.isSyncA()andSomething.cSyncA()
x.isSyncA() and x.isSyncB()
Cannot be accessed simultaneously. Because isSyncA() and isSyncB() share the same synchronization locks on the object instance x!
1 | public class LockTest1 { |
Results:
1 | t11 : isSyncA |
x.isSyncA() and y.isSyncA()
Can be accessed simultaneously. Because t21 and t22 don’t try to acquire the same lock. x.isSyncA() accesses the synchronization lock of x, and y.isSyncA() accesses the synchronization lock of y.
1 | public class LockTest2 { |
Results:
1 | t21 : isSyncA |
x.cSyncA() and y.cSyncB()
Cannot be accessed simultaneously. Because cSyncA() and cSyncB() are static, x.cSyncA() is equivalent to Something.isSyncA(), and y.cSyncB() is equivalent to Something.isSyncB(), so they share the same synchronization lock and cannot be acquired at the same time.
1 | public class LockTest3 { |
Results:
1 | t31 : cSyncA |
x.isSyncA() and Something.cSyncA()
Can be accessed simultaneously. Because isSyncA() is an instance method, x.isSyncA() uses the lock of object x; while cSyncA() is a static method, Something.cSyncA() uses class lock. Therefore, they can be accessed simultaneously.
1 | public class LockTest4 { |
Results:
1 | t41 : isSyncA |