Java basic tutorial on the synchronized keyword Java multithreading tutorial

  • 2020-04-01 02:44:21
  • OfStack

In this chapter, the synchronized keyword is introduced. The contents involved include:
1. The principle of synchronized
2. Basic rules of synchronized
3. Synchronized methods and synchronized code blocks
4. Instance and global locks

1. The principle of synchronized

In Java, each object has one and only one synchronization lock. This also means that synchronization locks are object-dependent.
When we call an object's synchronized method, we acquire the object's synchronized lock. For example, synchronized(obj) acquires a synchronized lock for "obj this object."
Access to synchronous locks by different threads is mutually exclusive. In other words, at a certain point in time, the object's synchronization lock can only be acquired by one thread! With synchronous locks, we can implement exclusive access to objects/methods in multiple threads. For example, there are now two threads A and B, both of which access the "synchronized lock for object obj". Suppose, at some point, thread A acquires the "obj's synchronous lock" and is doing something. At this point, thread B also attempts to acquire the "obj's synchronization lock" -- thread B fails to acquire the "obj's synchronization lock" and must wait until thread A releases the "obj's synchronization lock" before thread B can acquire the "obj's synchronization lock" and run.


2. Basic rules of synchronized

We summarize the basic rules of synchronized as the following three and illustrate them by example.
Article 1: when a thread accesses a synchronized method or block of synchronized code of an object, other threads' access to that synchronized method or block of synchronized code of that object will be blocked.
Article 2: when a thread accesses a synchronized method or a synchronized code block of an object, other threads can still access the object's synchronized code block.
Article 3: when a thread accesses a synchronized method or block of synchronized code of an object, other threads' access to other synchronized methods or blocks of synchronized code of that object will be blocked.

Article 1 with a

When a thread accesses a synchronized method or block of synchronized code of an object, access by other threads to that synchronized method or block of synchronized code of that object is blocked.
The following is a demo of the synchronized code block.


class MyRunable implements Runnable {

    @Override
    public void run() {
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); //Dormancy 100 ms
                    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();     //New "Runnable object"
        Thread t1 = new Thread(demo, "t1");  //Create a new "thread t1", which is based on the demo Runnable object
        Thread t2 = new Thread(demo, "t2");  //New "thread t2", t2 is based on the demo Runnable object
        t1.start();                          //Start "thread t1"
        t2.start();                          //Start "thread t2"
    } 
}

Operation results:


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:
There are "synchronized(this) code blocks" in the run() method, and both t1 and t2 are threads created based on "demo this Runnable object." This means that we can think of this in synchronized(this) as a "demo Runnable object"; Therefore, threads t1 and t2 share a "synchronized lock for the demo object." So, while one thread is running, the other thread must wait for the "run thread" to release the "demo synchronization lock" before it can run.

If you confirm, you've figured out the problem. So let's change the above code and run it and see what happens, and see if you get 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); //Dormancy 100 ms
                    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");  //New "thread t1"
        Thread t2 = new MyThread("t2");  //New "thread t2"
        t1.start();                          //Start "thread t1"
        t2.start();                          //Start "thread t2"
    } 
}

Code description:
Comparing Demo1_2 and Demo1_1, we find that the MyThread class in Demo1_2 is directly inherited from Thread, and t1 and t2 are both children of MyThread.
Fortunately, synchronized(this) is also called in "Demo1_2's run() method," just as "Demo1_1's run() method" also calls synchronized(this)!
So does Demo1_2 execute the same as Demo1_1?
Operation results:


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:
If this result doesn't surprise you at all, I'm sure you're already familiar with synchronized and this. Otherwise, read on for the analysis here.
This in synchronized(this) refers to the "current class object", that is, the current object corresponding to the class in which synchronized(this) resides. It is used to acquire a "synchronization lock for the current object".
In Demo1_2, this in synchronized(this) represents a MyThread object, and t1 and t2 are two different MyThread objects, so t1 and t2 acquire synchronized(this) synchronization locks for different objects when they execute synchronized(this). For Demo1_1 pairs, this in synchronized(this) represents a MyRunable object; T1 and t2 share a MyRunable object, so one thread acquires a synchronized lock on the object, causing another to wait.

Article 2:

When a thread accesses a synchronized method or a synchronized code block of an object, other threads can still access an asynchronous code block of that object.
The following is a demo of the synchronized code block.


class Count {
    //A method that contains a synchronized synchronized block
    public void synMethod() {
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); //Dormancy 100 ms
                    System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
    //Asynchronous methods
    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, which calls the synMethod() method of the count object
        Thread t1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.synMethod();
                    }
                }, "t1");
        //Create a new t2, which calls the nonSynMethod() method of the count object
        Thread t2 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.nonSynMethod();
                    }
                }, "t2");  

        t1.start();  //Start the t1
        t2.start();  //Start the t2
    } 
}

Operation results:


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:
There are two new child threads t1 and t2 in the main thread. T1 calls the synMethod() method of the count object, which contains the synchronization block. T2 calls the nonSynMethod() method of the count object, which is not a synchronous method. T1 runs, although synchronized(this) is called to get the "count's synchronized lock"; But it doesn't block t2 because t2 doesn't use a "count" synchronous lock.

Article 3 the

When a thread accesses a synchronized method or block of synchronized code of an object, other threads' access to other synchronized methods or blocks of synchronized code of that object is blocked.
We also modify the body of the nonSynMethod() method in the above example with synchronized(this). The modified source code is as follows:


class Count {
    //A method that contains a synchronized synchronized block
    public void synMethod() {
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); //Dormancy 100 ms
                    System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
    //Methods that also contain synchronized synchronized 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 a new t1, which calls the synMethod() method of the count object
        Thread t1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.synMethod();
                    }
                }, "t1");
        //Create a new t2, which calls the nonSynMethod() method of the count object
        Thread t2 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.nonSynMethod();
                    }
                }, "t2");  

        t1.start();  //Start the t1
        t2.start();  //Start the t2
    } 
}

Execution result:


synMethod() : 11
synBlock() : 3
 

4. Instance and global locks
Instance lock - locks on an instance object. If the class is a singleton, the lock also has the concept of a global lock.
The instance lock corresponds to the synchronized keyword.
Global lock - this lock is for a class and is Shared by threads regardless of how many objects are in the instance.
The global lock corresponds to static synchronized (or locked on a class or classloader object of that class).

A good example of an "instance lock" and a "global lock" :


pulbic class Something {
    public synchronized void isSyncA(){}
    public synchronized void isSyncB(){}
    public static synchronized void cSyncA(){}
    public static synchronized void cSyncB(){}
}

Suppose there are two instances of Something, x and y. Analyze the locks acquired by the following four sets of expressions.
(01) x.i sSyncA () and x.i sSyncB ()
(02) x.i sSyncA () and y.i sSyncA ()
(03) x.c SyncA () and y.c SyncB ()
(4) x.i sSyncA () with Something. CSyncA ()

(01) cannot be accessed simultaneously. Because isSyncA() and isSyncB() are synchronous locks that access the same object (object x)!


//LockTest2. Java source code
class Something {
    public synchronized void isSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); //Dormancy 100 ms
                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); //Dormancy 100 ms
                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); //Dormancy 100 ms
                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); //Dormancy 100 ms
                System.out.println(Thread.currentThread().getName()+" : cSyncB");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}
public class LockTest2 {
    Something x = new Something();
    Something y = new Something();
    //Comparison of (02) xissynca () and yissynca ()
    private void test2() {
        //New t21, t21 will call x.issynca ()
        Thread t21 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.isSyncA();
                    }
                }, "t21");
        //New t22, t22 will call xissyncb ()
        Thread t22 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        y.isSyncA();
                    }
                }, "t22");  

        t21.start();  //Start the t21
        t22.start();  //Start the t22
    }
    public static void main(String[] args) {
        LockTest2 demo = new LockTest2();
        demo.test2();
    }
}

Operation results:


t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB

(02) can be accessed simultaneously. Because it is not accessing the same object's synchronization lock, x.issynca () accesses the synchronization lock of x, while y.issynca () accesses the synchronization lock of y.

The same code at the page code block index 9

Operation results:


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 (), they share a synchronized lock and cannot be questioned at the same time.


//LockTest3. Java source code
class Something {
    public synchronized void isSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); //Dormancy 100 ms
                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); //Dormancy 100 ms
                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); //Dormancy 100 ms
                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); //Dormancy 100 ms
                System.out.println(Thread.currentThread().getName()+" : cSyncB");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}
public class LockTest3 {
    Something x = new Something();
    Something y = new Something();
    //Comparison of (03) x.csynca () with y.csyncb ()
    private void test3() {
        //New t31, t31 will call x.issynca ()
        Thread t31 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.cSyncA();
                    }
                }, "t31");
        //New t32, t32 will call xissyncb ()
        Thread t32 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        y.cSyncB();
                    }
                }, "t32");  

        t31.start();  //Start the no.t31.welcome
        t32.start();  //Start the t32
    }
    public static void main(String[] args) {
        LockTest3 demo = new LockTest3();
        demo.test3();
    }
}

Operation results:


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, xissynca () USES a lock on object x; Whereas cSyncA() is a static method, something.csynca () is understood to use a "class lock." Therefore, they can be accessed simultaneously.


//LockTest4. Java source code
class Something {
    public synchronized void isSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); //Dormancy 100 ms
                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); //Dormancy 100 ms
                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); //Dormancy 100 ms
                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); //Dormancy 100 ms
                System.out.println(Thread.currentThread().getName()+" : cSyncB");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}
public class LockTest4 {
    Something x = new Something();
    Something y = new Something();
    //Comparison between (04) xissynca () and something.csynca ()
    private void test4() {
        //New t41, t41 will call x.issynca ()
        Thread t41 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.isSyncA();
                    }
                }, "t41");
        //New t42, t42 will call xissyncb ()
        Thread t42 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Something.cSyncA();
                    }
                }, "t42");  

        t41.start();  //Start the t41
        t42.start();  //Start the t42
    }
    public static void main(String[] args) {
        LockTest4 demo = new LockTest4();
        demo.test4();
    }
}

Operation results:


t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA


Related articles: