Delve into the essentials of Java multithreaded concurrent programming

  • 2020-04-01 04:18:17
  • OfStack

The keyword synchronized
The synchronized key can modify functions, in-function statements. Whether it adds methods or objects, the lock it takes is an object, not a piece of code or a function.
1. When two concurrent threads access a synchronized(this) synchronized block of code in the same object object, only one thread can execute at a time, and the other thread can execute the code only after the current thread has finished executing.
2. When a thread accesses a synchronized(this) synchronized code block in an object, other threads can still access other non-synchronized (this) code blocks in the object.
3. The important thing to note here is that when a thread accesses a synchronized(this) block of the object, other threads' access to other synchronized(this) synchronized blocks of the object will be blocked.
4. The above also applies to other synchronized code blocks, that is, when a thread accesses a synchronized(this) synchronized code block of an object, the thread acquires the object's object lock. And each object (i.e., the class instance) corresponds to a lock, each synchronized (this) must invoke the code block (can function, also can be variable) of the lock of the object to perform, otherwise belongs to thread block, method once execution will monopolize the lock, until returned from the method, also release the lock, back into the executable. This mechanism ensures that at most one of all the member functions declared synchronized is executable at any one time for each object (because at most only one thread can acquire the lock for that object), thus avoiding access conflicts to class member variables.
Disadvantages of synchronized:
Since synchronized locks the object that calls the synchronized method, that is, when a thread P1 executes the method in different threads, they form a mutual exclusion that achieves the effect of synchronization. However, it is important to note that another object of the Class that this object belongs to can call this method with the synchronized keyword at will. The essence of a synchronized method is to apply the synchronized method to an object reference, which can only be invoked by a thread that has acquired a P1 object lock. In the case of P2, P1 has nothing to do with it. We will describe this situation in detail as follows:
First of all, we first introduce the synchronized keyword of two kinds of lock objects: objects and classes, synchronized can add lock or object class for the resource locks, class to lock all the objects of this class (instance), and object lock just for the class of a specified object locking, the rest of the class's object to an object before you can still use lock synchronized methods.
One of the main questions we'll discuss here is, "can the same class call the same method from different instances cause synchronization problems?"
The synchronization problem is only related to the resource, depending on whether the resource is static or not. The same static data, you have the same function in different threads that read and write to it, the CPU will not produce errors, it will ensure the execution logic of your code, and whether that logic is what you want, depends on what kind of synchronization you need. Even if you have two different pieces of code running in two different cores of the CPU and writing a memory address at the same time, the Cache mechanism will lock one in L2 first. Then update, and share to another core, you can't go wrong, otherwise Intel, amd will have so many people for nothing.
So, as long as you don't have the same resource or variable Shared by two code, you don't have inconsistent data. And calls to different objects of the same class have completely different stacks, which are completely irrelevant to each other.
As an example of a ticketing process, our Shared resource is the number of tickets left.


package com.test;

public class ThreadSafeTest extends Thread implements Runnable {
  
  private static int num = 1;

  public ThreadSafeTest(String name) {
    setName(name);
  }

  public void run() {
    sell(getName());   
  }
  
  private synchronized void sell(String name){
    if (num > 0) {
      System. out.println(name + ":  Test votes greater than 0" );
      System. out.println(name + ": t Collection in progress (approx 5 Done in seconds)... " );
      try {
        Thread. sleep(5000);
        System. out.println(name + ": t Print the ticket and sell the ticket " );
        num--;
        printNumInfo();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } else {
      System. out.println(name+":  There are no tickets. Stop selling " );
    }
  }
  
  private static void printNumInfo() {

    System. out.println(" System: current votes: " + num);
    if (num < 0) {
      System. out.println(" Warning: vote below 0 Is negative " );
    }
  }

  public static void main(String args[]) {
    try {
      new ThreadSafeTest(" The conductor li XX" ).start();
      Thread. sleep(2000);
      new ThreadSafeTest(" The king of the conductor X" ).start();
      
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

After running the above code, the output we get is:


 The conductor li XX:  Test votes greater than 0
 The conductor li XX:     Collection in progress (approx 5 Done in seconds)... 
 The king of the conductor X:  Test votes greater than 0
 The king of the conductor X:  Collection in progress (approx 5 Done in seconds)... 
 The conductor li XX:     Print the ticket and sell the ticket 
 System: current votes: 0
 The king of the conductor X:  Print the ticket and sell the ticket 
 System: current votes: -1
 Warning: vote below 0 Is negative 

According to the output, we can find that the remaining votes are -1, and there is a synchronization error. The reason for this is that we created two instance objects that modified the Shared static resource static int num = 1 at the same time. So we remove the static modifier from the box in the above code, and then run the program, we can get:


 The conductor li XX:  Test votes greater than 0
 The conductor li XX:     Collection in progress (approx 5 Done in seconds)... 
 The king of the conductor X:  Test votes greater than 0
 The king of the conductor X:  Collection in progress (approx 5 Done in seconds)... 
 The conductor li XX:     Print the ticket and sell the ticket 
 System: current votes: 0
 The king of the conductor X:  Print the ticket and sell the ticket 
 System: current votes: 0

After the degree changes, the program seems to run without any problems, and each object has its own stack, which runs separately. But this goes against what we want multiple threads to do with Shared resources at the same time (num goes from a Shared resource to a member variable for each instance when destatic), which is clearly not what we want.
In both cases, the main action is to lock the object. For the reasons I mentioned earlier, when two different instances of a class make changes to the same Shared resource, the CPU defaults to this in order to ensure that the logic of the program does so, and it is up to the programmer to decide whether or not it is the desired result. Therefore, we need to change the scope of the action of the lock, if the action object is only an instance, then this problem is inevitable; It is only when the scope of the lock is the entire class that it is possible to exclude different instances of the same class from modifying the Shared resource at the same time.


package com.test;

public class ThreadSafeTest extends Thread implements Runnable {
  private static int num = 1;

  public ThreadSafeTest(String name) {
    setName(name);
  }

  public void run() {
    sell(getName());   
  }  
  
  private synchronized static void sell(String name){

    if (num > 0) {
      System. out.println(name + ":  Test votes greater than 0" );
      System. out.println(name + ": t Collection in progress (approx 5 Done in seconds)... " );
      try {
        Thread. sleep(5000);
        System. out.println(name + ": t Print the ticket and sell the ticket " );
        num--;
        printNumInfo();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } else {
      System. out.println(name+":  There are no tickets. Stop selling " );
    }
  }

  private static void printNumInfo() {
    System. out.println(" System: current votes: " + num);
    if (num < 0) {
      System. out.println(" Warning: vote below 0 Is negative " );
    }
  }

  public static void main(String args[]) {
    try {
      new ThreadSafeTest(" The conductor li XX" ).start();
      Thread. sleep(2000);
      new ThreadSafeTest(" The king of the conductor X" ).start();
      
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

If the program is modified as above, the running results can be obtained:


 The conductor li XX:  Test votes greater than 0
 The conductor li XX:     Collection in progress (approx 5 Done in seconds)... 
 The conductor li XX:     Print the ticket and sell the ticket 
 System: current votes: 0
 The king of the conductor X:  There are no tickets. Stop selling 

Adding a static modifier to the sell() method makes the lock object a class that blocks other instances of the class when an instance of that class operates on a Shared variable. So that we can get the results we want on time.
Conclusion:
1. The synchronized keyword has two USES: a synchronized method and a synchronized block.
2. In Java, there are not only class instances, but also a lock for each class
When using the synchronized keyword, there are a few points to note:
1. The synchronized keyword cannot be inherited. While synchronized can be used to define methods, it is not part of the method definition, so the synchronized keyword cannot be inherited. If a method in a parent class USES the synchronized keyword and the method is overridden in a subclass, the method in the subclass is not synchronized by default and must be displayed with the synchronized keyword in the method in the subclass. Of course, you can also call the corresponding method in the parent class in the subclass, so that even though the method in the subclass is not synchronized, the subclass calls the synchronized method in the parent class, which is equivalent to the method in the subclass. Such as,
Add the synchronized keyword to a subclass:


class Parent { 
  public synchronized void method() {  } 
} 
class Child extends Parent { 
  public synchronized void method () {  } 
}

Call the superclass method:


class Parent { 
  public synchronized void method() {  } 
} 
class Child extends Parent { 
  public void method() { super.method();  } 
}

The synchronized keyword cannot be used in interface method definitions.
3. Constructors cannot use the synchronized keyword, but they can use synchronized blocks for synchronization.
4, synchronized positions are free to be placed, but cannot be placed after a method's return type.
5. The synchronized keyword should not be used to synchronize variables if the following code is incorrect:


public synchronized int n = 0; 
public static synchronized int n = 0;

6. Although the use of the synchronized keyword is the safest method of synchronization, it can cause unnecessary resource consumption and performance loss if used in large quantities. Synchronized locks a method on the surface, but actually locks a class. For example, the synchronized keyword is used for both nonstatic methods method1() and method2(), and when one method is executed, the other method cannot be executed. Static methods are similar to non-static methods. However, there is no interaction between static and non-static methods, as shown in the following code:


public class MyThread1 extends Thread { 
  public String methodName ; 
 
  public static void method(String s) { 
    System. out .println(s); 
    while (true ); 
  } 
  public synchronized void method1() { 
    method( " The static method1 methods " ); 
  } 
  public synchronized void method2() { 
    method( " The static method2 methods " ); 
  } 
  public static synchronized void method3() { 
    method( " static method3 methods " ); 
  } 
  public static synchronized void method4() { 
    method( " static method4 methods " ); 
  } 
  public void run() { 
    try { 
      getClass().getMethod( methodName ).invoke( this); 
    } 
    catch (Exception e) { 
    } 
  } 
  public static void main(String[] args) throws Exception { 
    MyThread1 myThread1 = new MyThread1(); 
    for (int i = 1; i <= 4; i++) { 
      myThread1. methodName = "method" + String.valueOf (i); 
      new Thread(myThread1).start(); 
      sleep(100); 
    } 
  } 
}

The running result is:


 The static method1 methods 
 static method3 methods 

As you can see from the above run, method2 and method4 won't run until method1 and method3 are all run. Therefore, it follows that if you define a non-static method in a class by using synchronized, it will affect all the non-static methods defined by synchronized in that class. If a static method is defined, it affects all static methods defined by synchronized in this class. This is a bit like a table lock in a data table, where when a record is modified, the system locks the entire table. Therefore, the heavy use of this synchronization method will cause the performance of the program to degrade significantly.
More secure synchronized access to Shared resources:
1. Define the private instance variable + its get method instead of the public/protected instance variable. If a variable is defined as public, the object can acquire it directly from the outside, bypassing the control of the synchronization method, and change it. This is also one of the standard implementations of javabeans.
2. If the instance variable is an object, such as a number group or an ArrayList, the above method is still not safe, because when the outside world gets the reference of the instance object through the get method and points it to another object, the private variable will also be changed, which is not dangerous. At this point you need to add the get method to synchronized and return only the clone() of the private object. This way, the caller only gets a reference to a copy of the object.

Three ways to get an object monitor (lock) by wait() and notify()
Calls to wait() and notify() in a thread method must specify an Object, and the thread must have the monitor for that Object. The easiest way to get an object monitor is to use the synchronized keyword on the object. When the wait() method is called, the thread releases the object lock and goes to sleep. When another thread calls the notify() method, the same Object Object must be used, and when the notify() method is successfully called, the corresponding isothermal thread on that Object will be awakened.
For multiple methods that are locked by an object, one of them is awakened when the notify() method is called, and notifyAll() wakes up all of its waiting threads.


package net.mindview.util;

import javax.swing.JFrame;

public class WaitAndNotify {
    public static void main(String[] args) {
      System. out.println("Hello World!" );
      WaitAndNotifyJFrame frame = new WaitAndNotifyJFrame();
      frame.setDefaultCloseOperation(JFrame. EXIT_ON_CLOSE);
       // frame.show();
      frame.setVisible( true);
   }
}

@SuppressWarnings("serial" )
class WaitAndNotifyJFrame extends JFrame {

    private WaitAndNotifyThread t ;

    public WaitAndNotifyJFrame() {
      setSize(300, 100);
      setLocation(250, 250);
      JPanel panel = new JPanel();
      JButton start = new JButton(new AbstractAction("Start") {
          public void actionPerformed(ActionEvent event) {
             if (t == null) {
                t = new WaitAndNotifyThread(WaitAndNotifyJFrame.this);
                t.start();
            } else if (t .isWait ) {
                t. isWait = false ;
                t.n();
                // t.notify();
            }
         }
      });
      panel.add(start);
      JButton pause = new JButton(new AbstractAction("Pause") {
          public void actionPerformed(ActionEvent e) {
             if (t != null) {
                t. isWait = true ;
            }
         }
      });
      panel.add(pause);
      JButton end = new JButton(new AbstractAction("End") {
          public void actionPerformed(ActionEvent e) {
             if (t != null) {
                t.interrupt();
                t = null;
            }
         }
      });
      panel.add(end);
      getContentPane().add(panel);
   }

}

@SuppressWarnings("unused" )
class WaitAndNotifyThread extends Thread {

    public boolean isWait ;
    private WaitAndNotifyJFrame control ;
    private int count ;

    public WaitAndNotifyThread(WaitAndNotifyJFrame f) {
       control = f;
       isWait = false ;
       count = 0;
   }

    public void run() {
       try {
          while (true ) {
             synchronized (this ) {
               System. out.println("Count:" + count++);
                sleep(100);
                if (isWait )
                  wait();
            }
         }
      } catch (Exception e) {
      }
   }
    
   public void n() {
       synchronized (this ) {
         notify();
      }
   }

}

As example, the code in the box above if remove the synchronized code block, execution will be thrown. Java lang. IllegalMonitorStateException anomalies.
Looking at the JDK, we can see that the reason for this exception is that the current thread is not the owner of the object monitor.
This method should only be invoked by a thread that is the owner of the object monitor, and can be made the owner of the object monitor by one of three methods:
1. By executing the synchronization instance method of this object, such as:
               


  public synchronized void n() {
     notify();
   }

2, by executing the body of the synchronized statement that is synchronized on this object, such as:
         


 public void n() {
     synchronized (this ) {
       notify();
     }
   }

3. For an object of Class type, you can execute a synchronous static method of that Class.
We do not necessarily create an instance object when we call a static method. Therefore, you cannot use this to synchronize static methods, so you must use the Class object to synchronize static methods. Since the notify() method is not a static method, we cannot set the n() method to be a static method.


public class SynchronizedStatic implements Runnable {

    private static boolean flag = true;

//Class object synchronization method 1:
   //Notice the synchronization method that is static, monitor: SynchronizedStatic. Class
    private static synchronized void testSyncMethod() {
       for (int i = 0; i < 100; i++) {
          try {
            Thread. sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System. out.println("testSyncMethod:" + i);
      }
   }


//Class object synchronization method 2:
      private void testSyncBlock() {
       //Display using the get class as the monitor. It is the same as the static synchronized method that implicitly retrieves the class monitor.
       synchronized (SynchronizedStatic. class) {
          for (int i = 0; i < 100; i++) {
             try {
               Thread. sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System. out.println("testSyncBlock:" + i);
         }
      }
   }


    public void run() {
       //Flag is a static variable. As a result, different threads execute different methods, which is the only way to see different locking effects.
       if (flag ) {
          flag = false ;
          testSyncMethod();
      } else {
          flag = true ;
         testSyncBlock();
      }
   }

    public static void main(String[] args) {
      ExecutorService exec = Executors. newFixedThreadPool(2);
      SynchronizedStatic rt = new SynchronizedStatic();
      SynchronizedStatic rt1 = new SynchronizedStatic();
      exec.execute(rt);
      exec.execute(rt1);
      exec.shutdown();
   }
}

The result of the above code is to have two synchronized methods print 100 Numbers from 0 to 99 at the same time. Method 2 shows the declaration that the scope of a code block is a class. The two methods work in the same way. Because methods 1 and 2 are classed, they are mutually exclusive, meaning that when a thread calls one of the two methods, the remaining uncalled methods also block the other thread. Therefore, the result of running the program will be:


testSyncMethod:0
testSyncMethod:1
... ...
testSyncMethod:99
testSyncBlock:0
... ...
testSyncBlock:99

However, if we replace the SynchronizedStatic. Class in method 2 with this, the two methods will not be mutually exclusive due to the lack of scope, and the output of the program will alternate, as shown below:


testSyncBlock:0
testSyncMethod:0
testSyncBlock:1
testSyncMethod:1
... ...
testSyncMethod:99
testSyncBlock:99

There are two kinds of scope of locks, one is the object of the class, the other is the class itself. In the above code, two methods are given to make the scope of the lock a class, so that different objects of the same class can be synchronized.
Summary above, need to pay attention to the following points:
1, wait () and notify () and notifyAll () needs to be on the premise of having object's monitor implementation, otherwise you will be thrown. Java lang. IllegalMonitorStateException anomalies.
2, multiple threads can wait on an object at the same time.
3. Notify () is to randomly wake up a waiting thread on an object, or do nothing if there is no waiting thread.
4, the notify() thread that wakes up does not wake up immediately after the notify() executes, but only after the notify() thread releases the object monitor.
5. These methods of Object are a long way from the methods of sleep and interrupt of Thread.


Related articles: