In this chapter, we will introduce synchronized keywords. The content involved includes:
1. Synchronized principle
2. Synchronized basic rules
3. Synchronized method and synchronized code block
4. Instance lock and global lock
1. Synchronized principle
In Java, each object has and only has one synchronization lock. This also means that the synchronization lock exists on the 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 the "obj object".
Access to synchronization locks by different threads is mutually exclusive. In other words, at a certain point in time, the object's synchronization lock can only be obtained by one thread! Through synchronization locks, we can achieve mutually exclusive access to "objects/methods" in multiple threads. For example, there are now two threads A and thread B, which both access the "synchronous lock of object obj". Suppose that at some point, thread A acquires the "obj's synchronization lock" and performs some operations; and at this time, thread B also attempts to obtain the "obj's synchronization lock" - Thread B will fail to acquire, it must wait, Thread B can only obtain the "obj's synchronization lock" until thread A releases the "synchronous lock of this object" and can only run.
2. Synchronized basic rules
We summarize the basic rules of synchronized into the following 3 and illustrate them through examples.
Article 1: When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads will be blocked from access to the "synchronized method" or "synchronized code block" of "the object".
Article 2: When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads can still access the asynchronized code block of "this object".
Article 3: When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads will be blocked from accessing other "synchronized methods" or "synchronized code block" of "the object". .
Article 1
When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads will be blocked from access to the "synchronized method" or "synchronized code block" of "the object".
Below is the demonstration program corresponding to "synchronized code block".
The code copy is as follows:
class MyRunable implements Runnable {
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class Demo1_1 {
public static void main(String[] args) {
Runnable demo = new MyRunable(); // Create a new "Runnable object"
Thread t1 = new Thread(demo, "t1"); // Create a new "thread t1", t1 is based on demo, the Runnable object
Thread t2 = new Thread(demo, "t2"); // Create new "thread t2", t2 is based on demo, Runnable object
t1.start(); // Start "thread t1"
t2.start(); // Start "thread t2"
}
}
Running results:
The code copy is as follows:
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
Results description:
There is a "synchronized(this) code block" in the run() method, and t1 and t2 are threads created based on the "demo" Runnable object. This means that we can regard this in synchronized(this) as "demo Runnable object"; therefore, threads t1 and t2 share "synchronous lock of the demo object". Therefore, when one thread is running, another thread must wait for the "running thread" to release the "demo synchronization lock" before it can run.
If you confirm, you figured out this problem. Then we modify the above code, and then run it to see how the result is, and see if you will be confused. The modified source code is as follows:
The code copy is as follows:
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class Demo1_2 {
public static void main(String[] args) {
Thread t1 = new MyThread("t1"); // Create new "thread t1"
Thread t2 = new MyThread("t2"); // Create new "thread t2"
t1.start(); // Start "thread t1"
t2.start(); // Start "thread t2"
}
}
Code description:
Comparing Demo1_2 and Demo1_1, we found that the MyThread class in Demo1_2 is directly inherited from Thread, and t1 and t2 are both MyThread child threads.
Fortunately, the "run() method of Demo1_2" also called synchronized(this), just as the "run() method of Demo1_1" also called synchronized(this)!
So, is the execution process of Demo1_2 the same as Demo1_1?
Running results:
The code copy is as follows:
t1 loop 0
t2 loop 0
t1 loop 1
t2 loop 1
t1 loop 2
t2 loop 2
t1 loop 3
t2 loop 3
t1 loop 4
t2 loop 4
Results description:
If this result does not surprise you at all, then I believe that you have a deeper understanding of synchronized and this. Otherwise, please continue reading the analysis here.
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 purpose is to obtain the "synchronous lock of the current object".
For Demo1_2, this in synchronized(this) represents the MyThread object, while t1 and t2 are two different MyThread objects. Therefore, when t1 and t2 execute synchronized(this), they acquire the synchronization locks of different objects. For the Demo1_1 pair, this in synchronized(this) represents the MyRunable object; t1 and t2 share a MyRunable object. Therefore, one thread acquires the object's synchronization lock, which will cause another thread to wait.
Article 2
When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads can still access the asynchronized code block of "this object".
Below is the demonstration program corresponding to "synchronized code block".
The code copy is as follows:
class Count {
// Method containing synchronized synchronization block
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// Asynchronous method
public void nonSynMethod() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
public class Demo2 {
public static void main(String[] args) {
final Count count = new Count();
// Create a new t1, t1 will call the synMethod() method of the "count object"
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod();
}
}, "t1");
// Create a new t2, t2 will call the nonSynMethod() method of the "count object"
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // Start t1
t2.start(); // Start t2
}
}
Running results:
The code copy is as follows:
t1 synMethod loop 0
t2 nonSynMethod loop 0
t1 synMethod loop 1
t2 nonSynMethod loop 1
t1 synMethod loop 2
t2 nonSynMethod loop 2
t1 synMethod loop 3
t2 nonSynMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 4
Results description:
Two new child threads t1 and t2 are created in the main thread. t1 will call the synMethod() method of the count object, which contains synchronization blocks; t2 will call the nonSynMethod() method of the count object, which is not a synchronization method. When 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.
Article 3
When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads access to other "synchronized methods" or "synchronized code block" of "the object" will be blocked.
We will also modify the nonSynMethod() method body in the above example with synchronized(this). The modified source code is as follows:
The code copy is as follows:
class Count {
// Method containing synchronized synchronization block
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// Also contains synchronized synchronization block method
public void nonSynMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
final Count count = new Count();
// Create a new t1, t1 will call the synMethod() method of the "count object"
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod();
}
}, "t1");
// Create a new t2, t2 will call the nonSynMethod() method of the "count object"
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // Start t1
t2.start(); // Start t2
}
}
(One time) execution result:
The code copy is as follows:
synMethod(): 11
synBlock(): 3
4. Instance lock and global lock
Instance Lock--Locked on an instance object. If the class is a singleton, then the lock also has the concept of a global lock.
The synchronized keyword corresponds to the instance lock.
Global Lock-- This lock is targeted at a class. No matter how many objects the instance is, the threads share the lock.
The global lock corresponds to static synchronized (or locked on the class or classloader object of this class).
There is a very vivid example of "instance lock" and "global lock":
The code copy is as follows:
pulbic class Something {
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
Suppose, Something has two instances x and y. Analyze the locks acquired by the following four sets of expressions.
(01) x.isSyncA() and x.isSyncB()
(02) x.isSyncA() and y.isSyncA()
(03) x.cSyncA() and y.cSyncB()
(04) x.isSyncA() and Something.cSyncA()
(01) Cannot be accessed simultaneously. Because isSyncA() and isSyncB() are both synchronization locks that access the same object (object x)!
The code copy is as follows:
// LockTest2.java source code
class Something {
public synchronized void isSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncA");
}
}catch (InterruptedException ie) {
}
}
public synchronized void isSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncB");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncA");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncB");
}
}catch (InterruptedException ie) {
}
}
}
public class LockTest2 {
Something x = new Something();
Something y = new Something();
// Compare (02) x.isSyncA() with y.isSyncA()
private void test2() {
// Create a new t21, and t21 will call x.isSyncA()
Thread t21 = new Thread(
new Runnable() {
@Override
public void run() {
x.isSyncA();
}
}, "t21");
// Create a new t22, and t22 will call x.isSyncB()
Thread t22 = new Thread(
new Runnable() {
@Override
public void run() {
y.isSyncA();
}
}, "t22");
t21.start(); // Start t21
t22.start(); // Start t22
}
public static void main(String[] args) {
LockTest2 demo = new LockTest2();
demo.test2();
}
}
Running results:
The code copy is as follows:
t11: isSyncA
t11: isSyncA
t11: isSyncA
t11: isSyncA
t11: isSyncA
t12: isSyncB
t12: isSyncB
t12: isSyncB
t12: isSyncB
t12: isSyncB
(02) Can be accessed at the same time. Because it is not accessing the synchronization lock of the same object, x.isSyncA() accesses the synchronization lock of x, while y.isSyncA() accesses the synchronization lock of y.
The code copy is as follows:
// LockTest2.java source code
class Something {
public synchronized void isSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncA");
}
}catch (InterruptedException ie) {
}
}
public synchronized void isSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncB");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncA");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncB");
}
}catch (InterruptedException ie) {
}
}
}
public class LockTest2 {
Something x = new Something();
Something y = new Something();
// Compare (02) x.isSyncA() with y.isSyncA()
private void test2() {
// Create a new t21, and t21 will call x.isSyncA()
Thread t21 = new Thread(
new Runnable() {
@Override
public void run() {
x.isSyncA();
}
}, "t21");
// Create a new t22, and t22 will call x.isSyncB()
Thread t22 = new Thread(
new Runnable() {
@Override
public void run() {
y.isSyncA();
}
}, "t22");
t21.start(); // Start t21
t22.start(); // Start t22
}
public static void main(String[] args) {
LockTest2 demo = new LockTest2();
demo.test2();
}
}
Running results:
The code copy is as follows:
t21: isSyncA
t22: isSyncA
t21: isSyncA
t22: isSyncA
t21: isSyncA
t22: isSyncA
t21: isSyncA
t22: isSyncA
t21: isSyncA
t22: isSyncA
(03) Cannot be accessed simultaneously. Because cSyncA() and cSyncB() are both static types, x.cSyncA() is equivalent to Something.isSyncA(), and y.cSyncB() is equivalent to Something.isSyncB(), so they share a synchronization lock and cannot be asked at the same time. .
The code copy is as follows:
// LockTest3.java source code
class Something {
public synchronized void isSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncA");
}
}catch (InterruptedException ie) {
}
}
public synchronized void isSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncB");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncA");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncB");
}
}catch (InterruptedException ie) {
}
}
}
public class LockTest3 {
Something x = new Something();
Something y = new Something();
// Compare (03) x.cSyncA() with y.cSyncB()
private void test3() {
// Create a new t31, and t31 will call x.isSyncA()
Thread t31 = new Thread(
new Runnable() {
@Override
public void run() {
x.cSyncA();
}
}, "t31");
// Create a new t32, and t32 will call x.isSyncB()
Thread t32 = new Thread(
new Runnable() {
@Override
public void run() {
y.cSyncB();
}
}, "t32");
t31.start(); // Start t31
t32.start(); // Start t32
}
public static void main(String[] args) {
LockTest3 demo = new LockTest3();
demo.test3();
}
}
Running results:
The code copy is as follows:
t31: cSyncA
t31: cSyncA
t31: cSyncA
t31: cSyncA
t31: cSyncA
t32: cSyncB
t32: cSyncB
t32: cSyncB
t32: cSyncB
t32: cSyncB
(04) 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() can understand that it is a "class lock" used. Therefore, they can be accessed simultaneously.
The code copy is as follows:
// LockTest4.java source code
class Something {
public synchronized void isSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncA");
}
}catch (InterruptedException ie) {
}
}
public synchronized void isSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : isSyncB");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncA");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // Sleep for 100ms
System.out.println(Thread.currentThread().getName()+" : cSyncB");
}
}catch (InterruptedException ie) {
}
}
}
public class LockTest4 {
Something x = new Something();
Something y = new Something();
// Compare (04) x.isSyncA() with Something.cSyncA()
private void test4() {
// Create a new t41, and t41 will call x.isSyncA()
Thread t41 = new Thread(
new Runnable() {
@Override
public void run() {
x.isSyncA();
}
}, "t41");
// Create a new t42, and t42 will call x.isSyncB()
Thread t42 = new Thread(
new Runnable() {
@Override
public void run() {
Something.cSyncA();
}
}, "t42");
t41.start(); // Start t41
t42.start(); // Start t42
}
public static void main(String[] args) {
LockTest4 demo = new LockTest4();
demo.test4();
}
}
Running results:
The code copy is as follows:
t41: isSyncA
t42: cSyncA
t41: isSyncA
t42: cSyncA
t41: isSyncA
t42: cSyncA
t41: isSyncA
t42: cSyncA
t41: isSyncA
t42: cSyncA