This is the third day of my participation in Gwen Challenge

1. Use Synchronized

A mutex ensures that only one thread can execute a method or block of code (used to manipulate shared variables) at a time. Synchronized can be used to modify methods, code blocks. It is divided into the following three types:

  1. Decorates instance methods. To lock the current instance object, the thread needs to acquire the lock of the instance object before entering the current method. This is called the object lock.
  2. Decorates static methods. The lock is placed on the class object of the preceding class, and the thread needs to acquire the lock on the class object before entering the static method. This is called the class lock.
  3. Decorates the code block. A lock on an instance object is called an object lock. A lock on the class object of a class, called a class lock, ensures that only one thread acquires the lock at a time.

1. Modify instance methods

Instance methods modified by synchronized are nonstatic methods in which a thread acquires an object lock when it acquires one.

public class SyncTask implements Runnable {

    static int count = 0;

    @Override
    public void run(a) {

        increase();
    }

    private synchronized void increase(a) {

        for (int i = 0; i < 1000000; i++) { count++; }}}/ / class
SyncTask syncTask =new SyncTask();

        Thread thread1=new Thread(syncTask,"thread1");
        Thread thread2=new Thread(syncTask,"thread2");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(" count = " + SyncTask.count);

/ / for the results
count  =   2000000
Copy the code

It can be concluded that when multiple threads operate the method of the same object, only one thread can acquire the lock at the same time in the instance method modified by synchronized. After the current thread completes the execution and releases the lock, other threads can compete for the lock and execute it.

Think about it: what happens when multiple threads manipulate synchronized modified methods of different objects?

public static void main(String[] args){
        
        Thread thread1=new Thread(new SyncTask(),"thread1");
        Thread thread2=new Thread(new SyncTask(),"thread2");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(" count = " + SyncTask.count);

    }

// Execution result
count  =   1197724
Copy the code

The code change above is that different threads manipulate different objects. Note that SyncTask’s synchronized modified methods operate internally on the static variable count. You can see that the result is not 2 million. Therefore, it can be concluded that different threads do not affect each other when they operate synchronized modification methods of different objects. The value of the static variable count is significantly different from what was expected because there are no competing locks. An increase method that is equivalent to different threads accessing the same object without the synchronized modifier.

2. Decorate static methods

Synchronized static method that obtains the class object lock of the current class, also known as the class lock. Static modified member variables and member methods are class-specific, so concurrent operations of static methods can be guaranteed through class objects. Note that there is no conflict between thread A’s operation on an instance object’s non-static synchronized method and thread B’s operation on an instance object’s static synchronize method. There will be no mutual exclusion. Because thread A operates on object locks, thread B operates on class locks.

public class SyncTask implements Runnable {

    static int count = 0;


    @Override
    public void run(a) {

        increase();
    }


    private synchronized  static void increase(a) {

        for (int i = 0; i < 1000000; i++) { count++; }}}// Execute the function
public static void main(String[] args){

        SyncTask syncTask = new SyncTask();

        Thread thread1=new Thread(new SyncTask(),"thread1");
        Thread thread2=new Thread(new SyncTask(),"thread2");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(" count = " + SyncTask.count);

    }
    
// Execution result
count  =   2000000
Copy the code

Synchronized modifies the static increase method and acquires a class lock, unlike instance methods, which acquire an object lock. If you operate on a class lock, only one thread acquires the lock at a time, whether you operate on different objects or the same object.

3. Embellish code blocks

If there are many methods in a method and only some parts need to be synchronized, the method of fast code synchronization can be adopted to realize partial code block synchronization. A block of synchronized code can use an object lock to hold the current object, or a class lock to hold the entire class.

public class SyncTask implements Runnable {

    static int count = 0;

    static  SyncTask syncTask = new SyncTask();


    @Override
    public void run(a) {

        increase();
    }


    private   void increase(a) {

        synchronized(syncTask){
            for (int i = 0; i < 1000000; i++) { count++; }}}}// Execute the function
public static void main(String[] args){

        SyncTask syncTask = new SyncTask();

        Thread thread1=new Thread(new SyncTask(),"thread1");
        Thread thread2=new Thread(new SyncTask(),"thread2");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(" count = " + SyncTask.count);

    }
    
// Execution result
 count  =   2000000
Copy the code

Synchronized locks the syncTask object in increase, and each thread entering the increase method must hold the syncTask lock to continue execution, otherwise it must wait. Synchronized can also use this to indicate that the current object is locked, but as we see in the execution function each thread operates on a different object, the result is no longer the expected result. We can use class objects (class locks) to ensure that only one thread acquires the lock at a time. In a static method, you can create a static member variable or use the class object directly to ensure synchronization, since static methods can only use static member variables and the class object.

4. Differences between class locks and object locks

  1. Synchronized can lock an object, this keyword, or a class object when multiple threads operate on the same object. Object locks, class locks are mutually exclusive.
  2. Synchronized can lock a static object or a class object that adopts a class when multiple threads operate on different objects. This keyword has no mutex effect because it applies to instances of the current thread.
  3. Multiple threads, whether operating on the same object or multiple objects, can lock a static object or a class object. For example, the syncTask object above belongs to an object lock. The class object of the class belongs to the class lock and can be used.

2. The principle of Synchronized

The implementation of synchronized is based on Monitor. Synchronized modifies synchronized methods differently than synchronized code blocks.

2.1 Synchronized modifies the code block principle

A block of synchronized code is created in the Run method of SyncTask

public class SyncTask implements Runnable {

    static int count = 0;

    static  SyncTask syncTask = new SyncTask();


    @Override
    public void run(a) {
        synchronized(syncTask){
            for (int i = 0; i < 1000000; i++) { count++; }}}}Copy the code

Decompilating the class file using javap-verbose class to get the bytecode, only the bytecode of the run method is captured:

 public void run(a);
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: getstatic     #2                  // Field syncTask:Lcom/mdy/lib/SyncTask;
         3: dup
         4: astore_1
         5: monitorenter       // Enter the synchronization method here
         6: iconst_0
         7: istore_2
         8: iload_2
         9: ldc           #3                  // int 1000000
        11: if_icmpge     28
        14: getstatic     #4                  // Field count:I
        17: iconst_1
        18: iadd
        19: putstatic     #4                  // Field count:I
        22: iinc          2.1
        25: goto          8
        28: aload_1
        29: monitorexit      // Exit the synchronization method here
        30: goto          38
        33: astore_3
        34: aload_1
        35: monitorexit     // Exit the synchronization method here
        36: aload_3
        37: athrow
        38: return

Copy the code

You can see this implemented using Monitorenter and MonitoreXit in the synchronized code block. Monitorenter points to the start of the synchronized code block and Monitorexit points to the end of the synchronized code block. When monitorenter arrives, the current thread attempts to acquire the objectref’s monitor. When the objectref’s monitor’s entry counter is 0, the current thread acquires the monitor, and the current thread acquires the lock. If the current thread already holds monitor, the monitor is re-entered with a counter value of +1. If another thread holds objectref’s monitor, the current thread is blocked. Until the other thread completes execution, when the Monitorexit directive is executed and the entry counter value is 0, the other thread will have the opportunity to hold the lock.

The understanding of the wait

ObjectMonitor() { ... _WaitSet = NULL; _EntryList = NULL; // Set of threads in the waiting block state... }Copy the code
  1. When a thread requests a lock, it enters the _EntryList collection to wait and compete for the lock.
  2. When a thread acquires a lock, _owner marks the thread that acquired the lock
  3. The thread that acquired the lock is callingwaitMethod, it releases the lock and enters _WaitSet, waiting to be woken up.
  4. When the thread in _WaitSet is awakened, it re-enters the _EntryList collection to compete for the lock.

2.2 Principle of Synchronized modification method

SyncTask uses synchronized to modify synchronization methods

public class SyncTask implements Runnable {

    static int count = 0;

    @Override
    public void run(a) {
        inCrease();
    }

    public synchronized  void inCrease(a){
        for (int i = 0; i < 1000000; i++) { count++; }}}Copy the code

Decompilating the class file using javap-verbose class to get the bytecode, only the bytecode of the inCrease method is captured:

 public synchronized void inCrease(a);
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: ldc           #3                  // int 1000000
         5: if_icmpge     22
         8: getstatic     #4                  // Field count:I
        11: iconst_1
        12: iadd
        13: putstatic     #4                  // Field count:I
        16: iinc          1.1
        19: goto          2
        22: return

Copy the code

In the decomcompiled bytecode, the synchronized synchronized method does not contain monitorenter and Monitorexit directives. ACC_SYNCHRONIZED is used to identify whether the method is a synchronized method.

2.3 Optimization of Synchronized

Before Java6, synchronized was a heavyweight lock. After Java6, biased locking and lightweight locking were added to reduce the performance cost of acquiring and releasing locks.