Java Multithreading Series--The synchronized keyword of "Basic" 04

Overview


In this chapter, the `synchronized keyword is introduced. The content involved includes:

  1. synchronized principle;
  2. synchronized basic rules;
  3. synchronized methods and synchronized code blocks;
  4. Instance locks and global locks.

synchronized principle


在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。

当我们调用某对象的 synchronized 方法时,就获取了该对象的同步锁。For example, synchronized(obj) acquires the synchronization lock on "the object obj".

不同线程对同步锁的访问是互斥的。That is to say, at a certain point in time, the synchronization lock of the object can only be acquired by one thread! Through synchronization locks, we can achieve mutually exclusive access to "objects/methods" in multiple threads. For example, there are now two thread A and thread B, both of which have access to the "synchronized lock on object obj". Suppose, at a certain moment, thread A acquires "obj's synchronization lock" and performs some operations; at this time, thread B also attempts to acquire "obj's synchronization lock" - thread B will fail to acquire, it must wait, Until thread A releases the "synchronization lock of the object", thread B cannot acquire the "synchronization lock of obj" and can run.

synchronized basic rules


We summarize the basic rules of synchronization into the following three, and illustrate them with examples:

  • Article 1: When a thread accesses the "synchronized method" or "synchronized code block" of "an object", other threads' access to the "synchronized method" or "synchronized code block" of "the object" will be blocked;
  • Item 2: When a thread accesses the "synchronized method" or "synchronized code block" of "an object", other threads can still access the unsynchronized code block of "the object";
  • Article 3: 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 .
Article 1

When a thread accesses the "synchronized method" or "synchronized code block" of "an object", other threads' access to the "synchronized method" or "synchronized code block" of "the object" will be blocked.

Below is the demo program corresponding to 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); // 休眠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();     // 新建“Runnable对象”

        Thread t1 = new Thread(demo, "t1");  // 新建“线程t1”, t1是基于demo这个Runnable对象
        Thread t2 = new Thread(demo, "t2");  // 新建“线程t2”, t2是基于demo这个Runnable对象
        t1.start();                          // 启动“线程t1”
        t2.start();                          // 启动“线程t2” 
    } 
}

operation result:

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

Result description:

  There is a "synchronized(this) code block" 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 "demo the Runnable object"; therefore, threads t1 and t2 share the "synchronization 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 the problem. Then we will modify the above code, and then run it again to see what 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); // 休眠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");  // 新建“线程t1”
        Thread t2 = new MyThread("t2");  // 新建“线程t2”
        t1.start();                          // 启动“线程t1”
        t2.start();                          // 启动“线程t2” 
    } 
}

Code description:

  Comparing Demo1_2 and Demo1_1, we find that the MyThread class in Demo1_2 directly inherits from Thread, and both t1 and t2 are MyThread child threads.

  Fortunately, synchronized(this) is also called in "Demo1_2's run() method", just as "Demo1_1's run() method" also calls synchronized(this)!

  So, is the execution process of Demo1_2 the same as Demo1_1?

operation result:

t1 loop 0
t2 loop 0
t2 loop 1
t1 loop 1
t2 loop 2
t1 loop 2
t1 loop 3
t2 loop 3
t2 loop 4
t1 loop 4

Result description:

  If this result does not surprise you at all, then I believe you have a deep understanding of 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 where synchronized(this) is located. Its role is to acquire the "synchronization lock on 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 pair, this in synchronized(this) represents the MyRunable object; t1 and t2 share the same MyRunable object, therefore, if one thread acquires the synchronization lock of the object, another thread will wait.

Article 2

When a thread accesses the "synchronized method" or "synchronized code block" of "an object", other threads can still access the unsynchronized code block of "the object".

Below is the demo program corresponding to the "synchronized code block".

class Count {

    // 含有synchronized同步块的方法
    public void synMethod() {
        synchronized (this) {
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 休眠100ms
                    System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
                }
            } catch (InterruptedException ie) {
            }
        }
    }

    // 非同步的方法
    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();
        // 新建t1, t1会调用“count对象”的synMethod()方法
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                count.synMethod();
            }
        }, "t1");

        // 新建t2, t2会调用“count对象”的nonSynMethod()方法
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                count.nonSynMethod();
            }
        }, "t2");

        t1.start(); // 启动t1
        t2.start(); // 启动t2
    }
}

operation result:

t2 nonSynMethod loop 0
t1 synMethod loop 0
t2 nonSynMethod loop 1
t1 synMethod loop 1
t2 nonSynMethod loop 2
t1 synMethod loop 2
t2 nonSynMethod loop 3
t1 synMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 4

Result description:

  Two new sub-threads t1 and t2 are created in the main thread. t1 will call the synMethod() method of the count object, which contains a synchronized block; and t2 will call the nonSynMethod() method of the count object, which is not a synchronized method. When t1 is running, although synchronized(this) is called to obtain the "count's 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 "an object", other threads' access to other "synchronized methods" or "synchronized code blocks" of "the object" will be blocked.

We also modify the nonSynMethod() method body in the above example with synchronized(this). The modified source code is as follows:

class Count {

    // 含有synchronized同步块的方法
    public void synMethod() {
        synchronized (this) {
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 休眠100ms
                    System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
                }
            } catch (InterruptedException ie) {
            }
        }
    }

    // 也包含synchronized同步块的方法
    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();
        // 新建t1, t1会调用“count对象”的synMethod()方法
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                count.synMethod();
            }
        }, "t1");

        // 新建t2, t2会调用“count对象”的nonSynMethod()方法
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                count.nonSynMethod();
            }
        }, "t2");

        t1.start(); // 启动t1
        t2.start(); // 启动t2
    }
}

operation result:

t2 nonSynMethod loop 0
t2 nonSynMethod loop 1
t2 nonSynMethod loop 2
t2 nonSynMethod loop 3
t2 nonSynMethod loop 4
t1 synMethod loop 0
t1 synMethod loop 1
t1 synMethod loop 2
t1 synMethod loop 3
t1 synMethod loop 4

Result description:

  Two new sub-threads t1 and t2 are created in the main thread. Both t1 and t2 call synchronized(this) at runtime, this is the Count object (count), and t1 and t2 share count. Therefore, when t1 is running, t2 will be blocked, waiting for t1 to release the "synchronization lock of the count object" before t2 can run.

synchronized methods and synchronized code blocks


synchronized方法The method is modified with synchronized, and synchronized代码块the code block is modified with synchronized.

synchronized method example

public synchronized void foo1() {
    System.out.println("synchronized methoed");
}

synchronized block

public void foo2() {
    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

this in a synchronized block refers to the current object. You can also replace this with other objects, for example, replace this with obj, then foo2() will acquire the synchronization lock of obj when it executes synchronized(obj).

Synchronized code blocks allow more precise control over conflict-restricted access areas, and sometimes perform more efficiently. Here is an example to demonstrate:

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(); // 获取当前时间(millis)
        demo.synMethod();                   // 调用“synchronized方法”
        diff = System.currentTimeMillis() - start; // 获取“时间差值”
        System.out.println("synMethod() : " + diff);

        start = System.currentTimeMillis(); // 获取当前时间(millis)
        demo.synBlock();                    // 调用“synchronized方法块”
        diff = System.currentTimeMillis() - start; // 获取“时间差值”
        System.out.println("synBlock()  : " + diff);
    }
}

(One time) execution result:

synMethod() : 2
synBlock()  : 1

Instance locks and global locks


Instance lock – lock on an instance object. If the class is a singleton, then the lock also has the concept of a global lock.
    Instance locks correspond to synchronizedkeywords.

Global lock – This lock is for the class and is shared by threads regardless of how many objects are instances.
    The global lock corresponds to static synchronized(or is locked on the class or classloader object of the class).

There is a very vivid example about 实例锁and :全局锁

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()与x.isSyncB() ;
  2. x.isSyncA()与y.isSyncA();
  3. x.cSyncA() and y.cSyncB();
  4. x.isSyncA()与Something.cSyncA()。
(01) cannot be accessed simultaneously. Because isSyncA() and isSyncB() are both synchronization locks that access the same object (object x)!
class Something {
    public synchronized void isSyncA() {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠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); // 休眠100ms
                System.out.println(Thread.currentThread().getName() + " : isSyncB");
            }
        } catch (InterruptedException ie) {
        }
    }
}

public class LockTest1 {

    Something x = new Something();
    Something y = new Something();

    // 比较(01) x.isSyncA()与x.isSyncB()
    private void test1() {
        // 新建t11, t11会调用 x.isSyncA()
        Thread t11 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncA();
            }
        }, "t11");

        // 新建t12, t12会调用 x.isSyncB()
        Thread t12 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncB();
            }
        }, "t12");

        t11.start(); // 启动t11
        t12.start(); // 启动t12
    }

    public static void main(String[] args) {
        LockTest1 demo = new LockTest1();
        demo.test1();
    }
}

operation result:

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 the synchronization lock of the same object is not accessed, x.isSyncA() accesses the synchronization lock of x, and y.isSyncA() accesses the synchronization lock of y.
class Something {
    public synchronized void isSyncA() {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠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); // 休眠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); // 休眠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); // 休眠100ms
                System.out.println(Thread.currentThread().getName() + " : cSyncB");
            }
        } catch (InterruptedException ie) {
        }
    }
}

public class LockTest2 {

    Something x = new Something();
    Something y = new Something();

    // 比较(02) x.isSyncA()与y.isSyncA()
    private void test2() {
        // 新建t21, t21会调用 x.isSyncA()
        Thread t21 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncA();
            }
        }, "t21");

        // 新建t22, t22会调用 y.isSyncA()
        Thread t22 = new Thread(new Runnable() {
            @Override
            public void run() {
                y.isSyncA();
            }
        }, "t22");

        t21.start(); // 启动t21
        t22.start(); // 启动t22
    }

    public static void main(String[] args) {
        LockTest2 demo = new LockTest2();

        demo.test2();
    }
}

operation result:

t21 : isSyncA
t22 : isSyncA
t22 : isSyncA
t21 : isSyncA
t21 : isSyncA
t22 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : 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 questioned at the same time .
class Something {
    public synchronized void isSyncA() {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠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); // 休眠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); // 休眠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); // 休眠100ms
                System.out.println(Thread.currentThread().getName() + " : cSyncB");
            }
        } catch (InterruptedException ie) {
        }
    }
}

public class LockTest3 {

    Something x = new Something();
    Something y = new Something();

    // 比较(03) x.cSyncA()与y.cSyncB()
    private void test3() {
        // 新建t31, t31会调用 x.isSyncA()
        Thread t31 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.cSyncA();
            }
        }, "t31");

        // 新建t32, t32会调用 y.isSyncB()
        Thread t32 = new Thread(new Runnable() {
            @Override
            public void run() {
                y.cSyncB();
            }
        }, "t32");

        t31.start(); // 启动t31
        t32.start(); // 启动t32
    }

    public static void main(String[] args) {
        LockTest3 demo = new LockTest3();

        demo.test3();
    }
}

operation result:

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 the object x; while cSyncA() is a static method, Something.cSyncA() can understand that it uses a "class lock". Therefore, they can be accessed simultaneously.
class Something {
    public synchronized void isSyncA() {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠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); // 休眠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); // 休眠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); // 休眠100ms
                System.out.println(Thread.currentThread().getName() + " : cSyncB");
            }
        } catch (InterruptedException ie) {
        }
    }
}

public class LockTest4 {

    Something x = new Something();
    Something y = new Something();

    // 比较(04) x.isSyncA()与Something.cSyncA()
    private void test4() {
        // 新建t41, t41会调用 x.isSyncA()
        Thread t41 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncA();
            }
        }, "t41");

        // 新建t42, t42会调用 x.isSyncB()
        Thread t42 = new Thread(new Runnable() {
            @Override
            public void run() {
                Something.cSyncA();
            }
        }, "t42");

        t41.start(); // 启动t41
        t42.start(); // 启动t42
    }

    public static void main(String[] args) {
        LockTest4 demo = new LockTest4();

        demo.test4();
    }
}

operation result:

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324844532&siteId=291194637