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
synchronized
keyword.
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 |