0. Regarding thread synchronization (1) Why do we need to synchronize multithreading?
Thread synchronization refers to allowing multiple running threads to cooperate well together to allow multiple threads to reasonably occupy and release resources as required. We use synchronization code blocks and synchronization methods in Java to achieve this goal. For example, solve the problem of multi-threaded unfixed order execution:
public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.start(); th2.start(); }}class MyThread2 extends Thread{ @Override public void run() { for( int i=0;i<10;i++) System. out.println( "thread 1 counter:"+i); }}class MyThread1 extends Thread{ @Override public void run() { for( int i=0;i<10;i++) System. out.println( "thread 1 counter:"+i); }}class MyThread1 extends Thread{ @Override public void run() { for( int i=0;i<10;i++) System. out.println( "thread 2 counter:"+i); } }The result of multi-threaded execution in this state is to randomly insert execution at will, which depends entirely on the JVM's thread scheduling. In many cases where orderly execution is required, this random execution state is obviously unsuitable.
public class ThreadTest { public static void main(String[] args) { MyThread thread = new MyThread(); Thread th1= new Thread(thread); Thread th2= new Thread(thread); th1.start(); th2.start(); }}class MyThread implements Runnable{ @Override public synchronized void run() { for( int i=0;i<10;i++) System. out.println(Thread. currentThread().getName()+" counter:"+i); }}After using the synchronization method, we can control the thread to exclusively occupy the execution body object. In this way, during the execution process, the thread can execute the tasks on the execution body at one time and exit the lock state. The JVM then dispatches another thread to run the tasks in the execution body at one time.
(2) The paradigm for thread creation and running:
In the past, we also had our own programming paradigm for thread creation and running. Generally, we defined an execution class to rewrite the run() method, but this method puts the execution body and the executed tasks together, which is not conducive to decoupling from the perspective of software engineering. The execution of a thread means that a thread executes a task of an object through the execution object. From this perspective, separating the task's prescriber from the execution class can make the various roles of multi-threaded programming clear and thus obtain good decoupling. The following is the programming paradigm for thread creation and execution:
public class FormalThreadClass { public static void main(String[] args) { Thread thread = new Thread( new MyRunnable()); thread.start(); }}class MyRunnable implements Runnable{ MyTask myTask = new MyTask(); @Override public void run() { myTask.doTask(); }}class MyTask{ public void doTask() { System. out.println( "This is real Tasking"); }}
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; at this time, thread B also attempts to acquire the "obj's synchronization lock" - Thread B will fail to acquire, it must wait until thread A releases the "obj's synchronization lock" and can only be 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".
(1) 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".
class MyRunable implements Runnable { @Override public void run() { synchronized(this) { try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // Sleep 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 the Runnable object Thread t2 = new Thread(demo, "t2"); // Create a new "thread t2", t2 is based on the Runnable object t1.start(); // Start "thread t1" t2.start(); // Start "thread t2" } } Running results:
t1 loop 0t1 loop 1t1 loop 2t1 loop 3t1 loop 4t2 loop 0t2 loop 1t2 loop 2t2 loop 3t2 loop 4
The result shows that 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:
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 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:
t1 loop 0t2 loop 0t1 loop 1t2 loop 1t1 loop 2t2 loop 2t1 loop 3t2 loop 3t1 loop 4t2 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.
(2) 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".
class Count { // Methods containing synchronized synchronization blocks 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 new t1, t1 will call the synMethod() method of "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:
t1 synMethod loop 0t2 nonSynMethod loop 0t1 synMethod loop 1t2 nonSynMethod loop 1t1 synMethod loop 2t2 nonSynMethod loop 2t1 synMethod loop 3t2 nonSynMethod loop 3t1 synMethod loop 4t2 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.
(3) 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:
class Count { // Methods containing synchronized synchronization blocks 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) { } } } // Methods containing synchronized synchronization blocks 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 t1, t1 will call the synMethod() method of the "count object" Thread t1 = new Thread( new Runnable() { @Override public void run() { count.syncMethod(); } }, "t1"); // Create 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:
t1 synMethod loop 0t1 synMethod loop 1t1 synMethod loop 2t1 synMethod loop 3t1 synMethod loop 4t2 nonSynMethod loop 0t2 nonSynMethod loop 1t2 nonSynMethod loop 2t2 nonSynMethod loop 3t2 nonSynMethod loop 4
Results description:
Two new child threads t1 and t2 are created in the main thread. Both t1 and t2 call synchronized(this), which is a Count object (count), and t1 and t2 share count. Therefore, when t1 is running, t2 will be blocked, and t1 will be run to release the "synchronous lock of the count object" before t2 can run.
3. Synchronized method and synchronized code block
The "synchronized method" uses synchronized modification method, while the "synchronized code block" uses synchronized modification code block.
Synchronized method example
public synchronized void foo1() { System.out.println("synchronized method");}synchronized code block public void foo2() { synchronized (this) { System.out.println("synchronized method"); }} This in the synchronized code block refers to the current object. This can also be replaced with other objects, such as this is replaced with obj, then foo2() acquires obj's synchronization lock when synchronized(obj).
Synchronized code blocks can control conflict-restricted access areas more accurately, and sometimes perform more efficiently. Here is an example to demonstrate:
// Demo4.java's source code public class Demo4 { public synchronized void synMethod() { for(int i=0; i<1000000; i++) ; } public void synBlock() { synchronized( this ) { for(int i=0; i<1000000; i++) ; } } public static void main(String[] args) { Demo4 demo = new Demo4(); long start, diff; start = System.currentTimeMillis(); // Get the current time (millis) demo.syncMethod(); // Call "synchronized method" diff = System.currentTimeMillis() - start; // Get "time difference" System.out.println("syncMethod() : "+ diff); start = System.currentTimeMillis(); // Get the current time (millis) demo.syncBlock(); // Call "synchronized method block" diff = System.currentTimeMillis() - start; // Get "time difference" System.out.println("syncBlock() : "+ diff); }} (One time) execution result:
synMethod(): 11synBlock(): 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.
(1) The synchronized keyword corresponds to the instance lock.
(2) Global lock--The 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":
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.
(1) x.isSyncA() and x.isSyncB()
(2) x.isSyncA() and y.isSyncA()
(3) x.cSyncA() and y.cSyncB()
(4) x.isSyncA() and Something.cSyncA()
(1) Cannot be accessed simultaneously.
Because isSyncA() and isSyncB() are both synchronization locks that access the same object (object x)!
// LockTest1.java's source code class Something { public synchronized void isSyncA(){ try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // Sleep 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 class LockTest1 { Something x = new Something(); Something y = new Something(); // Compare (01) x.isSyncA() with x.isSyncB() private void test1() { // Create new t11, t11 will call x.isSyncA() Thread t11 = new Thread( new Runnable() { @Override public void run() { x.isSyncA(); } }, "t11"); // Create new t12, t12 will call x.isSyncB() Thread t12 = new Thread( new Runnable() { @Override public void run() { x.isSyncB(); } }, "t12"); t11.start(); // Start t11 t12.start(); // Start t12 } public static void main(String[] args) { LockTest1 demo = new LockTest1(); demo.test1(); }} Running results:
t11 : isSyncAt11 : isSyncAt11 : isSyncAt11 : isSyncAt12 : isSyncBt12 : isSyncBt12 : isSyncBt12 : isSyncBt12 : isSyncBt12 : isSyncBt12 : isSyncBt12 : isSyncBt12 : isSyncB
(2) 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.
// LockTest2.java's source code class Something { public synchronized void isSyncA(){ try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // Sleep 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 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 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 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 new t21, t21 will call x.isSyncA() Thread t21 = new Thread( new Runnable() { @Override public void run() { x.isSyncA(); } }, "t21"); // Create new t22, 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:
t21 : isSyncAt22 : isSyncAt21 : isSyncAt22 : isSyncAt21 : isSyncAt22 : isSyncAt21 : isSyncAt22 : isSyncAt21 : isSyncAt22 : isSyncAt21 : isSyncAt22 : isSyncAt21 : isSyncAt22 : isSyncA
(3) 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(), they share a synchronization lock and cannot be asked at the same time.
// LockTest3.java's source code class Something { public synchronized void isSyncA(){ try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // Sleep 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 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 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 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 new t31, t31 will call x.isSyncA() Thread t31 = new Thread( new Runnable() { @Override public void run() { x.cSyncA(); } }, "t31"); // Create new t32, 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:
t31: cSyncAt31: cSyncAt31: cSyncAt31: cSyncAt31: cSyncAt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncBt32: cSyncB
(4) 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.
// LockTest4.java's source code class Something { public synchronized void isSyncA(){ try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // Sleep 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 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 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 new t41, t41 will call x.isSyncA() Thread t41 = new Thread( new Runnable() { @Override public void run() { x.isSyncA(); } }, "t41"); // Create new t42, 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:
t41: isSyncAt42: cSyncAt41: isSyncAt42: cSyncAt41: isSyncAt42: cSyncAt41: isSyncAt42: cSyncAt41: isSyncAt42: cSyncAt41: isSyncAt42: cSyncAt41: isSyncAt42: cSyncA