Concurrent programming (2): Synchronized keyword (the most comprehensive explanation of the interview)

The basic concept of Synchronized?

How is Synchronized used in the project?

The underlying implementation of Synchronized?

What are the optimizations of Synchronized JDK 1.6 version?

What is the difference between Synchronized and ReentrantLock?

 

 

One: basic concept

 

Synchronized: It is a lock (monitor lock) at the JVM level, reentrant, and unfair lock.

 

The synchronized keyword solves the synchronization of accessing resources between multiple threads. The synchronized keyword can ensure that the method or code block modified by it can only be executed by one thread at any time.

In addition, in the early versions of Java, synchronized is a heavyweight lock, which is inefficient because the monitor lock is implemented by the Mutex Lock of the underlying operating system. Java threads are mapped to the native threads of the operating system. Up. If you want to suspend or wake up a thread, you need to help the operating system to complete it, and the operating system needs to switch from user mode to kernel mode when switching between threads. The transition between this state requires a relatively long time and time cost Relatively high, which is why the early synchronized efficiency is low . Fortunately, after Java 6, Java officials optimized the synchronized greatly from the JVM level, so the efficiency of synchronized lock is also optimized very well now. JDK1.6 introduces a lot of optimizations for the implementation of locks, such as spin locks, adaptive spin locks, lock elimination, lock coarsening, bias locks, and lightweight locks to reduce the cost of lock operations.

 

Two: how to use it in the project

 

Used in the project: decorate instance methods, decorate static methods, decorate synchronous code blocks

 

  • Modified instance method: act on the current object instance to lock, before acquiring synchronization code to obtain the current object instance lock
  • Modified static method: that is, to lock the current class, it will affect all object instances of the class, because the static member does not belong to any instance object, is a class member (static indicates that this is a static resource of the class, no matter how new it is Objects, only one). So if a thread A calls the non-static synchronized method of an instance object, and thread B needs to call the static synchronized method of the class to which the instance object belongs, it is allowed, and no mutual exclusion will occur, because the lock occupied by accessing the static synchronized method is The lock of the current class, and the lock occupied by accessing the non-static synchronized method is the current instance object lock.
  • Modified code block: Specify the lock object, lock the given object, and obtain the lock of the given object before entering the synchronization code library.

 

to sum up: 

The synchronized keyword is added to the static method and synchronized (class) code block to lock the Class class. The synchronized keyword is added to the instance method to lock the object instance. Try not to use synchronized (String a) because in the JVM, the string constant pool has a cache function!

 

Handwriting a single-case model of a double-check lock

class DemoTest {// 懒加载
    // volatile 防止指令重排
    private static volatile DemoTest demotest;
    // 私有化构造
    private DemoTest(){
        
    }
    // 获取实例化对象函数
    private static DemoTest getInstance(){
        // 先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if(demotest == null){
           // 类对象加锁
            synchronized(DemoTest.class){
                if(demotest == null){
                    /**
                    * demotest = new DemoTest(); 
                    * 这段代码其实是分为三步执行:
                    * 1:为 uniqueInstance 分配内存空间
                    * 2:初始化 uniqueInstance
                    * 3:将 uniqueInstance 指向分配的内存地址
                    * 指令重排,可能导致执行顺序是1-3-2
                    * 线程 T1 执行了 1 和 3,
                    * 此时 T2 调用 getInstance() 后发现 demotest 不为空,
                    * 因此返回 demotest ,但此时 demotest 还未被初始化。
                    */
                    demotest = new DemoTest();
                }
            }
            
        }
        return demotest;
    }
    
}

to sum up:

It can be summarized that, in the case of multi-thread concurrency, adding volatile decoration can avoid obtaining uninstantiated objects caused by instruction rearrangement. See my other article for details: concurrent programming (1): volatile keyword

Three: the underlying implementation

 

private synchronized static void demoMethod(){
    System.out.println("静态同步方法");
}

public void mothod(){
    synchronized (this){
        System.out.println("同步代码块");
    }
}

 

View the instructions corresponding to the Class file;

javap -c -s DemoTest.class

 

The monitor instruction implements Synchronized thread synchronization: first, the monitorenter instruction sets the counter to 1, and the monitorexit instruction sets the counter to 0. When the counter value is 0, it indicates that the lock is not acquired and the lock can be acquired; To achieve thread synchronization of code blocks.

 

The synchronized synchronized statement block uses monitorenter and monitorexit instructions , where the monitorenter instruction points to the start position of the synchronized code block, and the monitorexit instruction indicates the end position of the synchronized code block. When the monitorenter instruction is executed, the thread tries to acquire the lock, that is, the monitor (monitor object exists in the object header of each Java object, the synchronized lock is acquired in this way, and why any object in Java can be used as a lock Cause) When the counter is 0, it can be successfully acquired. After the acquisition, the lock counter is set to 1, that is, increased by 1. Correspondingly, after executing the monitorexit instruction, the lock counter is set to 0, indicating that the lock is released. If acquiring the object lock fails, the current thread will block and wait until the lock is released by another thread.

 

FloatCloud

Synchronized modified synchronization method: It is through the ACC_SYNCHRONIZED access flag to determine whether the method is a synchronous method, and execute the response synchronous call operation.

 

Four: What optimizations have been done at the bottom of the synchronized keyword after JDK1.6

 

JDK1.6 introduces a lot of optimizations for the implementation of locks, such as bias locks, lightweight locks, spin locks, adaptive spin locks, lock elimination, lock coarsening and other technologies to reduce the cost of lock operations.

There are four main types of locks, in order: no lock state, bias lock state, light weight lock state, and heavyweight lock state. They will gradually upgrade with fierce competition. Note that locks can be upgraded and not downgraded. This strategy is to improve the efficiency of acquiring and releasing locks.

The introduction of specific locks will be explained in subsequent articles.

 

Five: the difference between synchronized and ReentrantLock

 

1: Both are reentrant locks

2: synchronized is a JVM lock (monitor lock) and depends on the JVM; ReentrantLock is a lightweight lock and depends on the API

3: ReentrantLock implements some advanced features:

Synchronized is an unfair lock. ReentrantLock can specify a fair lock or an unfair lock.

ReentrantLock provides a mechanism that can interrupt threads waiting for a lock.

ReentrantLock class combined with Condition instance can achieve "selective notification"

 

Both are reentrant locks . The concept of "reentrant lock" is: you can acquire your own internal lock again. For example, if a thread acquires a lock on an object and the object lock has not been released at this time, it can still be acquired when it wants to acquire the lock on the object again. If the lock cannot be reentered, it will cause a deadlock . Each time the same thread acquires a lock, the lock counter increments by 1, so the lock cannot be released until the lock counter drops to 0.

 

Synchronization is dependent on the JVM implementation . Earlier, we also mentioned that the virtual machine team made a lot of optimizations for the synchronized keyword in JDK1.6, but these optimizations are implemented at the virtual machine level and are not directly exposed to us. ReentrantLock is implemented at the JDK level (that is, at the API level, which requires lock () and unlock () methods to complete with try / finally statement blocks), so we can see how it is implemented by looking at its source code.

 

ReentrantLock adds some advanced features

  • ReentrantLock provides a mechanism that can interrupt threads waiting for a lock . This mechanism is implemented through lock.lockInterruptibly (). In other words, the waiting thread can choose to give up waiting and handle other things instead.
  • ReentrantLock can specify whether it is a fair lock or an unfair lock . Synchronized can only be an unfair lock. The so-called fair lock is that the thread waiting first acquires the lock first. ReentrantLock is not fair by default, and it can be determined whether it is fair through the ReentrantLock (boolean fair) constructor of the ReentrantLock class. The default is unfair lock.
  • The synchronized keyword is combined with the wait () and notify () / notifyAll () methods to implement the wait / notify mechanism . The ReentrantLock class can of course also be implemented, but it requires the Condition interface and the newCondition () method . Condition is only available after JDK1.5, and it has good flexibility. For example, it can implement multi-channel notification function, that is, multiple Condition instances (that is, object monitors) can be created in a Lock object, and thread objects can be registered in In the specified Condition, the thread notification can be selectively performed, and the scheduling thread is more flexible. When using the notify () / notifyAll () method for notification, the notified thread is selected by the JVM. The ReentrantLock class combined with the Condition instance can be used to implement "selective notification". This function is very important and is provided by the Condition interface by default. . The synchronized keyword is equivalent to only one Condition instance in the entire Lock object, and all threads are registered on it. If the notifyAll () method is executed, all the threads in the waiting state will be notified. This will cause a great efficiency problem, and the signalAll () method of the Condition instance will only wake up all the waiting threads registered in the Condition instance.

ReentrantLock (exclusive, reentrant lock) based on the AQS framework will be explained in detail in subsequent updates, insisting on summarizing and building your own knowledge tree system, and constantly updating the blog every day, hoping to see a different self! Everyone who loves technology, please welcome comments and point out the shortcomings of the article! Willing to encourage each other!

Published 27 original articles · praised 0 · visits 9931

Guess you like

Origin blog.csdn.net/weixin_38246518/article/details/105549263