The article reprinted to blog blog.csdn.net/javazejian/… Make it your own.

Thread safety is an important concern in concurrent programming. It should be noted that there are two main causes of thread safety problems, one is the existence of shared data (also known as critical resources), and the other is the existence of multiple threads operating on shared data. So in order to solve this problem, we may need to be such a scheme, when there are multiple threads share data operation, need to make sure that the same time one and only one thread in Shared data operation, other threads must wait until after the thread processing the data, this approach had a noble name named mutex, namely can achieve mutually exclusive access to the lock, In other words, when a shared data is mutex by the currently accessing thread, other threads can only wait until the current thread finishes processing and releases the lock. In Java, the keyword synchronized ensures that only one thread can execute a method or block of code at any one time. It is also important to note that synchronized has another important function. Synchronized is also important in that it ensures that changes made by one thread (primarily shared data) are seen by other threads (guaranteed visibility, a complete substitute for Volatile).

### Three application modes of synchronized The following three application modes of synchronized are introduced respectively

  • Modifier instance method, used to lock the current instance, before entering the synchronization code to obtain the current instance lock

  • Modifies a static method that locks the current class object before entering the synchronized code

  • Modifies a block of code that specifies a lock object, locks a given object, and acquires the lock for the given object before entering a synchronized code base.

#####synchronized applies to instance methods. An instance object lock uses synchronized to modify instance methods in an instance object

Public class AccountingSync implements Runnable{// Static int I =0; /** * synchronized public synchronized voidincrease(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0; j<1000000; j++){ increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync instance=new AccountingSync(); Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } /** * Output: * 2000000 */}Copy the code

In the above code, we have two threads operating on the same shared resource, variable I, since I ++; Does not have the atomic operation, the operation is to read the values first, then write back to a new value, equivalent to the original value plus 1, done in two steps, if the second thread to read the old values and write back to the first thread reads the I during the period of the new value domain values, then the second thread will see the same value with the first thread, and perform the same value plus one operation, This is a thread-safe failure, so you must use the synchronized modifier for the increase method to keep thread-safe. Note that synchronized modifiers the instance method increase, in which case the lock on the current thread is the instance object. In Java, thread-synchronization locks can be any object. This is indeed true from the code execution results; if we hadn’t used the synchronized keyword, the final output would probably have been less than 2,000,000, which is where the synchronized keyword comes in. Here we also need to realize that when one thread is accessing an object’s synchronized instance method, other threads cannot access the object’s other synchronized methods. After all, an object has only one lock, and when one thread acquires the lock, other threads cannot acquire the lock. So the object’s other synchronized instance methods cannot be accessed, but other threads can still access the object’s other non-synchronized methods. Of course, if thread A needs to access the synchronized method f1 of obj1 (the current lock is obj1), and thread B needs to access the synchronized method f2 of obj2 (the current lock is obj2), This is allowed because the two instance locks are not identical. In this case, thread-safety is guaranteed if two threads are operating on data that is not shared. Unfortunately, thread-safety may not be guaranteed if two threads are operating on data that is shared, as the following code demonstrates

public class AccountingSyncBad implements Runnable{
    static int i=0;
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0; j<1000000; j++){ increase(); }} public static void main(String[] args) throws InterruptedException {//new New instance Thread T1 =new Thread(new) AccountingSyncBad()); Threadt2 =new Thread(new AccountingSyncBad()); t1.start(); t2.start(); Thread A waits for thread A to terminate before returning t1.join() from thread.join(); t2.join(); System.out.println(i); }}Copy the code

The difference is that we create two new instances AccountingSyncBad at the same time, and then start two different threads to operate on the shared variable I. Unfortunately, the result is 1452317 instead of 2000000, because the code made a serious error. Although we modify the increase method with synchronized, two different instance objects are new, which means that there are two different instance object locks, so T1 and T2 enter their own object locks, which means that t1 and T2 threads use different locks, so thread safety cannot be guaranteed. The solution to this dilemma is to use synchronized on a static increase method, in which case the object lock is unique to the current class object, since no matter how many instance objects are created, there is only one for the class object. Let’s take a look at using a static increase method that applies synchronized.

#####synchronized when synchronized applies to a static method, the lock is the class object lock of the current class. Because static members are not exclusive to any instance object and are class members, concurrent operations on static members can be controlled through class object locks. Note that if thread A calls the non-static synchronized method of an instance object and thread B calls the static synchronized method of the class that the instance object belongs to, the mutual exclusion will not occur. Because a lock used to access a static synchronized method is the current class object, and a lock used to access a non-static synchronized method is the current instance object lock, look at the code below

public class AccountingSyncClass implements Runnable{ static int i=0; Public static synchronized void */ AccountingSyncClass */ static synchronized voidincrease(){
        i++;
    }

    /**
     * 非静态,访问时锁不一样不会发生互斥
     */
    public synchronized void increase4Obj(){
        i++;
    }

    @Override
    public void run() {
        for(int j=0; j<1000000; j++){ increase(); }} public static void main(String[] args) throws InterruptedException {//new New instance Thread T1 =new Thread(new) AccountingSyncClass()); Threadt2 =new Thread(new AccountingSyncClass()); // Start thread t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}Copy the code

Because the synchronized keyword modifies static increase methods, its lock object is the class object of the current class, unlike the synchronized method that modifies instance methods. Note that the increase4Obj method in this code is an instance method whose object lock is the current instance object. If it is called by another thread, there will be no mutual exclusion (lock objects are different, after all), but we should be aware that thread-safety issues can be found in this case (handling the shared static variable I).

#####synchronized In addition to using keywords to modify instance methods and static methods, you can also use synchronized code blocks. In some cases, the method body may be large, some time-consuming operations may occur, and only a small portion of the code needs to be synchronized. If the whole method is synchronized directly, the gain may not be worth the loss. In this case, we can use the synchronized code block to wrap the code to be synchronized, so that there is no need to synchronize the whole method. An example of the synchronized code block is as follows:

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run() {// omit other time-consuming operations.... Instance synchronized(instance){for(int j=0; j<1000000; j++){ i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}Copy the code

It can be seen from the code that synchronized is applied to a given instance object, that is, the current instance object is the lock object. Each time a thread enters the code block wrapped by synchronized, the current thread is required to hold the instance object lock. If other threads currently hold the lock, New threads must wait, ensuring that only one thread executes i++ at a time; Operation. In addition to instance as an object, we can also use this object (representing the current instance) or the current class object as the lock, as follows:

Synchronized (this){synchronized(this){for(int j=0; j<1000000; j++){ i++; Synchronized (accountingsync.class){for(int j=0; j<1000000; j++){ i++; }}Copy the code

After understanding the basic meaning and usage of synchronized, let’s take a closer look at the underlying implementation principles of synchronized.

Synchronization in Java virtual machines is implemented based on entry and exit Monitor objects. This is true whether synchronization is explicit (with explicit Monitorenter and Monitorexit directives, i.e. blocks of synchronized code) or implicit. In the Java language, synchronization is probably the most commonly used synchronization method modified by synchronized. The synchronized methods are not synchronized by monitorenter and Monitorexit directives, but are implicitly implemented by method-calling directives that read the ACC_SYNCHRONIZED flags of methods in the runtime constant pool, more on this later. Let’s start with a concept called Java object headers, which are key to understanding how synchronized works.

If you have any questions about the results of Synchronized, don’t worry. Let’s take a look at the principle of Synchronized, and then go back to the above problems. Let’s start by decompiling the following code to see how Synchronized implements block synchronization:

public class SynchronizedDemo {
     public void method() {
         synchronized (this) {
            System.out.println("Method 1 start"); }}}Copy the code

Decompilation result:

For the purpose of these two instructions, we refer directly to the JVM specification:

# # # # # monitorenter:

Each object is associated with a monitor. A monitor is locked if and only ifit has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor is associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread isthen• If the thread already owns the monitor associated with objectref, it reenters the monitor, • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
Copy the code

The passage roughly means:

Each object has a monitor lock. The monitor is locked when it is occupied, and the thread attempts to acquire ownership of the Monitor when it executes the Monitorenter instruction as follows:

  1. If monitor’s number of entries is 0, the thread enters Monitor, then sets the number of entries to 1, and the thread is the owner of Monitor.

  2. If the thread already owns the monitor and simply re-enters, the number of entries into the monitor is increased by one.

  3. If another thread has occupied monitor, the thread blocks until the number of monitor entries is zero, and then tries again to acquire ownership of monitor.

# # # # # monitorexit:

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
Copy the code

The passage roughly means:

The thread executing monitorexit must be the owner of the monitor to which objectref corresponds.

When the instruction is executed, the number of monitor entries decreases by 1. If the number of monitor entries decreases by 1, the thread exits the monitor and is no longer the owner of the monitor. Other threads blocked by the monitor can try to take ownership of the monitor.

Synchronized semantics are implemented through a monitor object. In fact, wait/notify and other methods also rely on monitor objects. This is why only in the synchronized block or method calls to wait/notify method, otherwise will be thrown. Java lang. The cause of the exception IllegalMonitorStateException.

Let’s look at the decompilation result of the synchronous method:

The source code:

public class SynchronizedMethod {
     public synchronized void method() {
        System.out.println("Hello World!"); }}Copy the code

Decompilation result:

As a result of the decompile, the method is not synchronized through monitorenter and Monitorexit (which, in theory, could be), but has the ACC_SYNCHRONIZED flag in its constant pool as opposed to the regular method. The JVM synchronizes methods based on this identifier: When a method is invoked, the calling instruction checks whether the ACC_SYNCHRONIZED access flag of the method is set. If so, the thread of execution obtains monitor, executes the method body after the method is successfully acquired, and releases Monitor after the method is executed. During method execution, the same Monitor object is no longer available to any other thread. There is essentially no difference, except that method synchronization is done implicitly, without bytecode.

##### Reentrancy of synchronized The design of mutex blocks when one thread attempts to manipulate a critical resource of an object lock held by another thread. However, when a thread requests to hold the critical resource of the object lock again, this situation belongs to reentrant lock and the request will succeed. In Java, synchronized is an atomic-based internal locking mechanism and can be reentrant. Therefore, it is allowed for a thread to call another synchronized method on the same object while calling a synchronized method in its method body. In other words, it is allowed for a thread to obtain an object lock and then request the object lock again, which is the reentrancy of synchronized. As follows:

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    static int j=0;
    @Override
    public void run() {
        for(int j=0; j<1000000; Synchronized (this){i++; increase(); }}} public synchronized voidincrease(){ j++; } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}Copy the code

As demonstrated in the code, after obtaining the current instance lock, the synchronized code block is entered to execute the synchronized code, and another synchronized method of the current instance object is called in the code block. When the current instance lock is requested again, it will be allowed to execute the method body code, which is the most direct embodiment of reentrant lock. Special attention is paid to the fact that when a subclass inherits from its parent, it can also call its parent’s synchronized methods via a reentrant lock. Note that since synchronized is implemented based on monitor, the counter in Monitor is still incremented by one with each reentrant. ##### thread interrupts and synchronized ###### Thread interrupts Interrupt a thread in the middle of a run method, as the word interrupts implies. In Java, there are three ways to interrupt a thread

// Interrupt Thread (instance method) public void thread.interrupt (); // check whether the Thread isInterrupted(example method) public Boolean thread.isinterrupted (); Public static Boolean thread.interrupted (); // Check whether the interrupted state is interrupted and clear the current interrupted state (static method).Copy the code

When a Thread is blocked or attempting to perform a blocking operation, interrupt the Thread with Thread.interrupt(). Note that an InterruptedException is thrown and the interrupted state is reset. The following code demonstrates this process:

public class InterruputSleepThread3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                //whileIn a try, we can exit the run loop by breaking the try {while (trueTimeunit.seconds.sleep (2); timeunit.seconds.sleep (2); } } catch (InterruptedException e) { System.out.println("Interruted When Sleep"); boolean interrupt = this.isInterrupted(); // The interrupt state is reset system.out.println ("interrupt:"+interrupt); }}}; t1.start(); TimeUnit.SECONDS.sleep(2); // Interrupts the blocked thread t1.interrupt(); /** * Interruted When Sleep interrupt:false* /}}Copy the code

As shown in the code above, we create a thread and use the sleep method to put it into a blocking state. After starting the thread, the interrupt method on the thread instance object is called to interrupt the blocking exception and throw InterruptedException. The interrupted state is reset. Some of you may wonder why thread. sleep(2000) is not used; But with the TimeUnit. SECONDS. Sleep (2); The reason is simple: the former is used without explicit units, while the latter is very explicit in units of seconds, and in fact the internal implementation of the latter eventually calls Thread.sleep(2000). Timeunit.seconds.sleep (2) is recommended for more semantic clarity. Note that TimeUnit is an enumeration type. In this case, calling thread.interrupt () directly to interrupt the Thread will get no response. This code will not interrupt the Thread in the non-blocking state:

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run() {while(true){
                    System.out.println("Not interrupted"); }}}; t1.start(); TimeUnit.SECONDS.sleep(2); t1.interrupt(); /** * Output result (infinite execution): not interrupted not interrupted not interrupted...... * /}}Copy the code

Although the interrupt method is called, thread T1 is not interrupted because a thread in a non-blocking state requires us to manually detect the interrupt and terminate the program. The improved code looks like this:

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run() {while(true){// Determine whether the current thread is interruptedif (this.isInterrupted()){
                        System.out.println("Thread interrupt");
                        break;
                    }
                }

                System.out.println("Loop out, thread interrupted!"); }}; t1.start(); TimeUnit.SECONDS.sleep(2); t1.interrupt(); /** * Output: thread interrupt has broken out of the loop, thread interrupt! * /}}Copy the code

Yes, we use the example method isInterrupted in our code to determine if the thread has been interrupted, and if interrupted it will jump out of the loop and end the thread. One is when a thread is blocking or trying to perform a blocking operation. We can interrupt it with the example method interrupt(). InterruptException (which must be caught and cannot be thrown) is thrown and the interrupt status is reset. Another way to interrupt a thread while it is running is to call the instance method Interrupt (), but the interrupt status must be determined manually. And write code that interrupts the thread (essentially terminating the body of the run method). Sometimes we need to do both of these things in our code, so we can write:

public void run(){try {// Check whether the current thread has been interrupted. Note that the interrupted method is static and will reset the interrupted state after being interruptedwhile(! Thread.interrupted()) { TimeUnit.SECONDS.sleep(2); } } catch (InterruptedException e) { } }Copy the code

#### interrupts and synchronized In fact, thread interrupts do not apply to synchronized methods or blocks of code on which the lock object is awaiting acquisition. That is, with synchronized, if a thread is awaiting a lock, there are only two results. Either it acquires the lock and continues, or it saves the wait, which does not take effect even if it calls a method that interrupts the thread. The demo code is as follows

public class SynchronizedBlocked implements Runnable{

    public synchronized void f() {
        System.out.println("Trying to call f()");
        while(true) // Never releases lock Thread.yield(); } /** * create a new thread in the constructor and start acquiring the object lock */ publicSynchronizedBlocked() {// The thread already holds the current instance lock newThread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }
    public void run() {// interrupt the judgmentwhile (true) {
            if (Thread.interrupted()) {
                System.out.println("Interrupt thread!!");
                break;
            } else{ f(); } } } public static void main(String[] args) throws InterruptedException { SynchronizedBlocked sync = new SynchronizedBlocked(); Thread t = new Thread(sync); T.start (); t.start(); TimeUnit.SECONDS.sleep(1); // Rupt thread, no effect t.innterrupt (); }}Copy the code

The SynchronizedBlocked constructor creates a new thread and initiates the fetch call f() to acquire the current instance lock. Since SynchronizedBlocked is itself a thread, it calls f() in its run method after starting, but because the object lock is occupied by another thread, (t) (lock path (); But you can’t interrupt the thread.

This article mainly refers to notify/notifyAll and wait methods. To use these three methods, you must be in a synchronized code block or a synchronized method. Otherwise you will be thrown IllegalMonitorStateException is unusual, this is because the call before this several methods must take the current monitor the monitor the object, that is to say wait and notify/notifyAll method relies on the monitor object, in the previous analysis, We know that monitor exists in the Mark Word of the object header (where the monitor reference pointer is stored), and that the synchronized keyword retrieves monitor, This is why notify/notifyAll and wait methods must be called in a synchronized code block or synchronized method.

synchronized (obj) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }
Copy the code

The wait method releases the monitor lock until notify/notifyAll is called. The wait method releases the monitor lock until notify/notifyAll is called. The sleep method only lets the thread sleep and does not release the lock. In addition, the notify/notifyAll method does not release the monitor lock immediately, but automatically releases the monitor lock after synchronized(){}/synchronized.

Java virtual machine optimization for Synchronized

There are four lock states: unlocked, biased, lightweight and heavyweight. Competition with the lock, the lock can be biased locking to upgrade to the lightweight lock, and then upgrade the heavyweight locks, lock upgrade is one-way, that is to say, can only upgrade from low to high, not be downgraded, lock on heavyweight locks, we had a detailed analysis of the front, now we will introduce biased locking and lightweight locks and other means of optimization, the JVM This section is not intended to go into the implementation and conversion process of each lock. It is more about the core optimization idea of each lock provided by the Java VIRTUAL machine. After all, the specific process is complicated.

Biased locking

Biased locking is to join the new lock after Java 6, it is a kind of for locking operation means of optimization, through the study found that in most cases, the lock does not exist a multithreaded competition not only, and always by the same thread for many times, so in order to reduce the same thread locks (will involve some CAS operation, time-consuming) introduced by the cost of biased locking. Biased locking the core idea is that if a thread got a lock and then lock into bias mode, the structure of Mark Word become biased locking structure, when the thread lock request again, no need to do any synchronization operation, namely the process of acquiring a lock, which saves a large amount of relevant lock application operation, thus the performance of the provider. Therefore, in the case of no lock contention, biased locking has a good optimization effect, after all, it is very likely that the same thread applies for the same lock for many consecutive times. But for lock more competitive situation, biased locking failure, because such occasions is likely to lock the thread is not the same for each application, so this situation should not be used to lock, or you will do more harm than good, it is important to note that the biased locking failed, does not immediately into a heavyweight locks, but upgraded to a lightweight lock first. Let’s move on to lightweight locks.

Lightweight lock

If biased locking fails, the virtual machine does not immediately upgrade to heavyweight locking, but instead attempts to use an optimization called lightweight locking (added after 1.6), in which the Mark Word structure also changes to lightweight locking. Lightweight locks improve application performance on the basis that “for the vast majority of locks, there is no competition for the entire synchronization cycle”, note that this is empirical data. It is important to understand that lightweight locks are suitable for scenarios where threads alternately execute synchronized blocks. If the same lock is accessed at the same time, this will cause the lightweight lock to expand into a heavyweight lock.

spinlocks

When lightweight locking fails, the virtual machine also performs an optimization called spin locking to prevent threads from actually hanging at the operating system level. This is based on in most cases, the thread holding the lock time not too long, if hang directly operating system level thread may do more harm than good, after all, the operating system to realize the need when switching between threads from user mode to kernel mode, the state transitions between needs a relatively long time, time cost is relatively high, So spinlock will assume that in the near future, the current thread lock can be obtained, therefore the virtual opportunity for current wants to make a few empty thread fetching the lock loop (which is also called spin), generally not too long, may be 50 or 100 cycles, after several cycles, if get the lock, I successfully enter the critical section. If the lock is not available, threads are suspended at the operating system level, which is an optimized way to spin locks, which can actually improve efficiency. Finally there is no way to upgrade to the heavyweight lock.

Lock elimination

Eliminate another lock lock is virtual machine optimization and the optimization more thoroughly, the Java virtual machine in the JIT compiler (can be simple to understand for the first time a piece of code will be executed when compile, also known as instant compiled), run through the context of the scan, remove lock there can be no Shared resource competition, in this way to eliminate unnecessary lock, The append of the following StringBuffer is a synchronous method, but in the add method the StringBuffer is a local variable and cannot be used by other threads, so it cannot compete for shared resources. The JVM automatically removes the lock.

Heavyweight lock

Synchronized, waiting for a thread to release a lock before acquiring a resource.

The main reference materials of this article: “Java Programming Ideas”, “Deep Understanding of Java Virtual Machine”, “Practical Java High Concurrency Programming”