introduce

This is an interview question I was asked in July and August last year. To be honest, I was surprised to be asked this question. I felt that this question was useless until I was asked a wave of new objects, Integer objects and so on. What could go wrong? Why synchronized performance has increased dramatically since java6? I was completely blinded. Here is a detailed summary

Synchronized usage

We know that the source of all the problems with concurrent programming is visibility, atomicity, orderliness. Synchronized can guarantee visibility, atomicity and order at the same time. Synchronized is often used to solve concurrency problems, as are many other tools, such as volatile. But volatile only guarantees visibility, atomicity, not order. See my previous article

Understand the volatile keyword from a computer perspective

Synchronized can be used in the following ways

  1. Modifier instance method that locks the current instance object this
  2. Modifies static methods that lock the Class object of the current Class
  3. Modifies a code block that specifies a lock object and locks the given object

Decorated instance method

public class SynchronizedDemo {
    
    public synchronized void methodOne() {
    
    }
}Copy the code

Decorated static methods

public class SynchronizedDemo {

    public static synchronized void methodTwo() {

    }
}Copy the code

Modify code block

SynchronizedDemo public void SynchronizedDemo {SynchronizedDemo public void SynchronizedDemo public void SynchronizedDemo public void SynchronizedDemo public void SynchronizedDemo public void SynchronizedDemo public void SynchronizedDemo Synchronized (synchronizeddemo.class) {}}Copy the code

Implementation principle of synchronized

Java object Composition

We all know that objects are stored in heap memory, and objects can be roughly divided into three parts, namely object header, instance variable and fill byte

  • Mark Word; Klass Pointer; Klass Pointer Klass Point is a pointer to an object’s class metadata that the virtual machine uses to determine which class the object is an instance of. Mark Word is used to store runtime data for the object itself
  • Instance variable, which holds the attribute data of the class, including the attribute information of the parent class. This part of the memory is aligned with 4 bytes
  • Padding data, because the VM requires that the object’s start address be a multiple of 8 bytes. Padding data does not have to exist, just for byte alignment

Both modification methods and code blocks are synchronized by holding the lock of the modified object. Where does synchronized lock object exist? Mark Word is the header of the lock object. What does Mark Word store?

Because the object header information is an additional storage cost unrelated to the data defined by the object itself, the Mark Word is designed to be a non-fixed data structure to store more valid data for the space efficiency of the JVM. It reuses its storage space based on the state of the object itself, that is, The Mark Word changes as the program runs, and the change status is as follows (for 32-bit VMS) :

Lightweight and biased locks are new additions to Java 6’s optimization of synchronized locks, which we’ll briefly examine later. Here we focus on heavyweight locks, also known as synchronized object locks, with a lock identifier of bit 10, where the pointer points to the starting address of a monitor object (also known as a pipe or monitor lock). Each object has a Monitor associated with it. In the Java virtual machine (HotSpot), monitor is implemented by ObjectMonitor. Its main data structure is as follows (located in the HotSpot virtual machine source code ObjectMonitor. HPP file, implemented in C++), with some attributes omitted

ObjectMonitor() { _count = 0; _recursions = 0; // Lock reentrant count _owner = NULL; // point to thread holding ObjectMonitor object _WaitSet = NULL; _WaitSet _EntryList = NULL; // The thread waiting for the lock will be added to the list}Copy the code

Explain the execution process in terms of thread states. (See “Understanding the Java Virtual Machine” for more information about state transitions.)

  1. New: a thread that has not been started after it is created
  2. Runnable includes Running and Ready in the operating system thread state
  3. Waiting indefinitely, not allocated CPU time, Waiting to be explicitly woken up by another thread. For example, call object.wait () with no Timeout parameter set
  4. Timed Waiting A Timed wait in which CPU execution time is not allocated but is automatically woken up by the system after a certain amount of time without Waiting for another thread to display a wake up. For example, call thread.sleep ()
  5. A thread is Blocked. The difference between a “Blocked” state and a “wait state” is that a “Blocked” state is waiting to acquire an exclusive lock, an event that will occur when another thread abandons the lock, whereas a “wait state” is waiting for a period of time, or a wake-up action. The thread enters this state while the program is waiting to enter the synchronization zone
  6. Terminated: The thread ends execution

For a synchronized modified method (block) :

  1. When multiple threads access the method at the same time, they are placed in the _EntryList queue and the thread is blocked
  2. When a thread has acquired the object’s monitor, it can enter the running state and execute the method. At this point, the /owner of the ObjectMonitor object points to the current thread, and count + 1 indicates that the current object lock has been acquired by a thread
  3. When a running thread calls wait(), the current thread releases the monitor object and enters waiting with the/of the ObjectMonitor objectOwner becomes null,The count minus 1, while the thread entersWaitSet queue until a thread calls notify() to wake it up, and the thread entersEntryList queue, compete to lock and then enter the _Owner section **
  4. If the current thread completes, the monitor object is also released, and the /owner of the ObjectMonitor object becomes null, and count is reduced by 1

From this point of view, a Monitor object exists in the object header of every Java object (storing Pointers), which is how synchronized locks are acquired, and why any object in Java can be used as a lock. It is also the reason why notify/notifyAll/ Wait methods exist in the top-level Object

How does synchronized acquire monitor objects?

So how does synchronized acquire monitor objects?

Synchronized modifies code blocks

public class SyncCodeBlock { public int count = 0; public void addOne() { synchronized (this) { count++; }}}Copy the code

javac SyncCodeBlock.java
javap -v SyncCodeBlock.classCopy the code

The decompiled bytecode is as follows

public void addOne(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: Monitorenter // Enter the synchronization method 4: aload_0 5: dup 6: getField #2 // Field count:I 9: iconST_1 10: iadd 11: Putfield #2 // Field count:I 14: ALOad_1 15: monitorexit // Exit the synchronization method 16: goto 24 19: astore_2 20: ALOAD_1 21: Monitorexit // Exit synchronization method 22: ALOad_2 23: athrow 24: return Exception table:Copy the code

You can see that the monitorexit directive enters the synchronized block, executes monitorenter, exits the synchronized block, and executes Monitorexit. You can see that there are two Monitorexit directives. The first one exits normally and the second one executes when an exception occurs

Synchronized modification method

public class SyncMethod { public int count = 0; public synchronized void addOne() { count++; }}Copy the code

The decompiled bytecode is as follows

public synchronized void addOne(); Descriptor: ()V // method identifier ACC_PUBLIC stands for public modifier, ACC_SYNCHRONIZED indicates that the method is synchronized flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field count:I 5: iconst_1 6: iadd 7: putfield #2 // Field count:I 10: return LineNumberTable:Copy the code

We don’t see monitorenter and Monitorexit directives. How do we synchronize? You can see that the method is identified as ACC_SYNCHRONIZED, indicating that this is a synchronized method

Lock the upgrade

In earlier versions of Java, synchronized was a heavyweight lock, which was inefficient because the operating system needed to switch from user state to core state, which took a relatively long time and cost a lot of time. After Java 6, partial locking and lightweight locking were introduced to reduce the performance cost of acquiring and releasing locks. In Java 6, partial locking and lightweight locking were introduced

Synchronized locks have four states: no lock, biased lock, lightweight lock, and heavyweight lock. These states will gradually upgrade with the competition status. The lock can be upgraded but cannot be degraded, but the biased lock can be reset to no lock

Biased locking

Why introduce biased locking?

After a lot of research by HotSpot author, it is found that there is no lock contention in most of the time, and usually a thread obtains the same lock for many times. Therefore, if the lock contention is needed every time, it will increase a lot of unnecessary cost. In order to reduce the cost of acquiring the lock, biased lock is introduced.

Bias locking principle and upgrade process

When thread 1 accesses the code block and acquires the lock object, it will record the threadID of the biased lock in the Java object header and stack frame, because the biased lock will not release the lock actively. Therefore, when thread 1 acquires the lock again in the future, it needs to compare whether the threadID of the current thread is consistent with the threadID in the Java object header. If they are the same (thread 1 still acquires the lock object), CAS is not required to lock and unlock the lock. If it is inconsistent (other threads, such as thread 2, compete for the lock object, and the biased lock is not actively released, so it is still the stored threadID of thread 1), then we need to check if thread 1 is alive as recorded in the Java object header. If not, then the lock object is reset to lock free. Other threads (thread 2) can compete to set it to a biased lock; If alive, then immediately find the thread (thread 1) stack frame of the information, if still need to continue to hold the lock object, so to suspend the current thread 1, cancel the biased locking, upgrade for lightweight lock, if thread 1 no longer use the lock object, then the lock object set as unlocked state, to a new thread.

Lightweight lock

Why lightweight locks?

Lightweight locks consider situations where there are not many threads competing for the lock object and the thread does not hold the lock for a long time. If the lock is released shortly after the block, the cost is not worth the cost. Therefore, the thread should not be blocked at all, and let it spin to wait for the lock to be released.

Lightweight locking principle and upgrade process

When thread 1 obtains the lightweight lock, it first copies the lock object header MarkWord into the space created by thread 1’s stack frame to store the lock record (called DisplacedMarkWord). Then CAS is used to replace the contents of the object header with the address of the DisplacedMarkWord stored by thread 1;

If thread 1 copy objects of head at the same time (before the thread 1 CAS), thread 2 is also preparing to get lock, copy the object head to thread 2 lock records in the space, but at the time of thread 2 CAS, find thread 1 had the object changed, thread 2 CAS fails, then thread 2 attempts to use spinlocks to waiting thread 1 releases the lock. A spin lock simply means that thread 2 repeats CAS through a loop

But if the spin too long also not line, because the spin is consumes CPU, thus the number of spin is limited, such as 10 times or 100 times, if you spin the thread 1 times haven’t release the lock, or thread 1 is still in execution, thread 2 still spin waiting, then another thread 3 competition the lock object, At this point the lightweight lock will expand to the heavyweight lock. Heavyweight locks block all threads except those that own the lock, preventing the CPU from idling.

Advantages and disadvantages of several locks

Best practices for using locks

Wrong lock position 1

synchronized (new Object())Copy the code

Each call creates a different lock, which is equivalent to no lock

Wrong locking position 2

private Integer count;
synchronized (count)Copy the code

String and Boolean are both implemented using the meta-mode, i.e. objects within a certain range are the same. So it looks like we’re using different objects, but we’re actually using the same object. Can cause a lock to be used in more than one place

Java constant pool details, seconds to understand all kinds of object equal operation

Correct locking position

Private final Object lock = new Object(); Private static final Object lock = new Object();Copy the code

Use of Lock interfaces

With synchronized, why provide a Lock interface? You might say that the Lock interface is better than synchronized. This was true prior to JDK1.5, but after JDK1.6 the performance was similar. Go straight to the Lock interface definition and see what features it has more than synchronized.

Public interface Lock {// void Lock (); Void lockInterruptibly() throws InterruptedException; // Non-blocking get lock Boolean tryLock(); Boolean tryLock(long time, TimeUnit Unit) throws InterruptedException; // Unlock void unlock(); // define the blocking Condition Condition newCondition(); }Copy the code

You can see that the Lock interface has many more features than synchronized. Explain the method in detail

  1. The lock() method, which acquires the lock and waits if it is acquired by another thread, is used in conjunction with the unlock method to actively release the lock. When an exception occurs, the lock is not actively released, so the lock release operation is placed in the finally block
  2. The lockInterruptibly() method, which responds to interrupts, the wait state of the interrupted thread, if the thread is waiting to acquire the lock while the lock is being acquired. Thus, when two threads attempt to acquire A lock using lock.lockinterruptibly () at the same time, calling threadb.interrupt () on threadB interrupts the wait if thread A has acquired the lock while threadB is waiting
  3. The tryLock() method, used to attempt to acquire the lock, returns true on success. Return false on failure to get. This means that the method will return immediately anyway. You don’t wait around until you get the lock
  4. TryLock (long time, TimeUnit Unit) method, similar to tryLock(). The difference is that this method will wait a certain amount of time before the lock is retrieved, and returns false if the lock is not retrieved within the specified time limit. Returns true if the lock was acquired initially or while waiting
  5. Unlock () method
  6. The newCondition() method defines the condition

The rest should make sense, as demonstrated by the lockInterruptibly() and newCondition() methods

LockInterruptibly () method

ReentrantLock myLock = new ReentrantLock(); Mylock. lock(); mylock. lock(); Thread thread = new Thread(() -> { try { // myLock.lock(); myLock.lockInterruptibly(); } catch (Exception e) { e.printStackTrace(); } finally {/ / when using myLock lockInterruptibly () / / will be thrown when the Java. Lang. InterruptedException, print over. / / using myLock lock (), has been blocking locks, Will not print over system.out.println ("over"); }}); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); TimeUnit.SECONDS.sleep(100);Copy the code

The use of the Condition

Synchronized combined with wait() and Nitofy ()/notifyAll() can realize the wait/notification model. ReentrantLock can also realize the wait/notification model, but it needs to use Condition, and Condition has better flexibility, which is embodied in the following

  1. Multiple Condition instances can be created within a Lock to implement multiple notification
  2. When notify() is used, the notified thread is randomly selected by the Java VIRTUAL machine, but ReentrantLock and Condition allow selective notification
public class WaitNotify { static ReentrantLock lock = new ReentrantLock(); static Condition conditionA = lock.newCondition(); static Condition conditionB = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread waitThreadA = new Thread(new WaitA(), "WaitThreadA"); waitThreadA.start(); Thread waitThreadB = new Thread(new WaitB(), "WaitThreadB"); waitThreadB.start(); TimeUnit.SECONDS.sleep(2); lock.lock(); try { conditionA.signal(); } finally { lock.unlock(); } } static class WaitA implements Runnable { @Override public void run() { lock.lock(); try { System.out.println(Thread.currentThread() + " begin await @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); conditionA.await(); System.out.println(Thread.currentThread() + " end await @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } static class WaitB implements Runnable { @Override public void run() { lock.lock(); try { System.out.println(Thread.currentThread() + " begin await @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); conditionB.await(); System.out.println(Thread.currentThread() + " end await @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }}}}Copy the code

Thread[WaitThreadA,5,main] begin await @ 00:49:57
Thread[WaitThreadB,5,main] begin await @ 00:49:57
Thread[WaitThreadA,5,main] end await @ 00:49:59Copy the code

The WaitThreadB is blocked because it is not notified

Finally, the similarities and differences between synchronized and ReentrantLock are summarized

  1. ReentrantLock supports non-blocking lock acquisition and can respond to interrupts, whereas synchronized does not
  2. ReentrantLock must manually acquire and release locks, whereas synchronized does not
  3. ReentrantLock can be a fair lock or an unfair lock, whereas synchronized can be an unfair lock
  4. Synchronized automatically releases a thread-owned lock when an exception occurs. ReentrantLock may cause a deadlock if it is not unlocked through UNLOCK. Therefore, the lock must be released in the finally block
  5. Synchronized and ReentrantLock are reentrant locks

Welcome to attention

Pay attention to replyPDF catalogSurprise, massive access to video resourceswww.erlie.cc