Keyword synchronized
The synchronized key can modify functions and statements within functions. Whether it is added to methods or objects, the lock it acquires is an object, rather than treating a piece of code or function as a lock.
1. When two concurrent threads access the synchronized (this) synchronized code block in the same object, only one thread can be executed for a period of time, and the other thread can only execute this code after the current thread has completed execution. .
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 this object.
3. It should be noted here that when a thread accesses a synchronized (this) code block of an object, other threads will be blocked from accessing other synchronized (this) synchronized code blocks in this object.
4. The above is also applicable to other synchronization code blocks, that is, when a thread accesses a synchronized (this) synchronization code block of an object, the thread obtains the object's object lock. Moreover, each object (i.e., class instance) corresponds to a lock. Each synchronized(this) must obtain the lock of the object that calls the code block (can be a function or a variable) to be executed, otherwise the thread to which it belongs is blocked. Once the method is executed, the lock will be exclusively occupied until it is released when it returns from the method and re-enteres the executable state. This mechanism ensures that at the same time, for each object, at most one of all member functions declared synchronized is in an executable state (because at most one thread can acquire the lock of the object), thus avoiding access to class member variables conflict.
Disadvantages of synchronized method:
Since synchronized locks the object that calls this synchronization method, that is, when a thread P1 executes this method in different threads, they will form mutual exclusions, thereby achieving the effect of synchronization. But it should be noted here that another object of Class that is of this object can arbitrarily call this method with the synchronized keyword added. The essence of the synchronization method is to apply synchronized on the object reference. Only threads that have obtained the P1 object lock can call this synchronized method. For P2, P1 has nothing to do with it. The program may also get rid of synchronization in this case. The control of the mechanism causes data confusion. We will explain this situation in detail below:
First, let's introduce two locked objects with the synchronized keyword: object and class - synchronized can add object locks or class locks to resources. Class locks work on all objects (instances) of this class, and object locks are just Locking for a specified object of this class, other objects of this class can still use the synchronized method that has locked the previous object.
One of the main issues we discuss here is: "Will the same class and different instances call the same method, will there be a synchronization problem?"
The synchronization problem is only related to the resource, and it depends on whether the resource is static. For the same static data, your same function belongs to different threads to read and write it at the same time, and the CPU will not generate errors. It will ensure the execution logic of your code. Whether this logic is what you want depends on you. What kind of synchronization is needed. Even if you have two different codes 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 it with another core, and there will be no errors, otherwise Intel or Amd will be in vain.
Therefore, as long as you don't have the same resource or variable that two code shares, there will be no data inconsistency. Moreover, calls to different objects of the same class have completely different stacks, and they are completely irrelevant.
Here we use an example to illustrate the ticket sales process, where our shared resources are the remaining number of tickets.
package com.test;public class ThreadSafeTest extends Thread implements Runnable { private static int num = 1; public ThreadSafeTest(String name) { setNa me(name); } public void run() { sell(getName()); } private synchronized void sell(String name){ if (num > 0) { System. out.println(name + ": The number of detection votes is greater than 0"); System. out.println(name + ": /t is collecting (completed in about 5 seconds ). . " ); try { Thread. sleep(5000); System. out.println(name + ": /tPrint the ticket, ticket sales are completed" ); num--; printNumInfo(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System. out.println(name+": No tickets, stop ticket sales" ); } } private static void printNumInfo() { System. out.println("System: Current Number of votes: " + num); if (num < 0) { System. out.println("Warning: votes are lower than 0, negative number occurs" ); } } public static void main(String args[]) { try { new ThreadSafeTest(" Ticket Seller Li XX" ).start(); Thread. sleep(2000); new ThreadSafeTest("Telephone Seller Wang X" ).start(); } catch (InterruptedException e) { e.printStackTrace(); } }} Run the above code and the output we get is:
Ticket Conductor Li XX: The number of test tickets is greater than 0 Ticket Conductor Li XX: The payment is being collected (completed in about 5 seconds). . . Ticket Seller King X: The number of test tickets is greater than 0 Ticket Seller King X: The payment is being collected (completed in about 5 seconds). . . Ticket seller Li XX: Print the bill, ticket sales completion system: Current number of votes: 0 Ticket seller Wang X: Print the bill, ticket sales completion system: Current number of votes: -1 Warning: The number of votes is lower than 0, negative numbers appear
Based on the output results, we can find that the remaining votes are -1, and there is a synchronization error problem. The reason for this is that the two instance objects we created have modified the shared static resource static int num = 1 at the same time. Then we remove the modifier static in the box in the above code, and then run the program to get:
Ticket Conductor Li XX: The number of test tickets is greater than 0 Ticket Conductor Li XX: The payment is being collected (completed in about 5 seconds). . . Ticket Seller King X: The number of test tickets is greater than 0 Ticket Seller King X: The payment is being collected (completed in about 5 seconds). . . Ticket Seller Li XX: Print the bill, ticket sales completion system: Current number of tickets: 0 Ticket Seller Wang X: Print the bill, ticket sales completion system: Current number of tickets: 0
After modifying the degree, the program runs without any problems. Each object has its own different stack and runs independently. But this goes against our expectation that multiple threads can handle shared resources at the same time (after static, num changes from shared resources to member variables owned by each instance), which is obviously not what we want.
In the above two codes, the main thing to adopt is to lock the object. For the reason I mentioned before, when two different instances of a class modify the same shared resource, the CPU will default to the program's logic. As for whether the desired result is the program's only The officer decides for himself. Therefore, we need to change the scope of the lock. If the target is just an instance, then this problem is inevitable. Only when the scope of the lock is the entire class, can different instances of the same class be excluded from sharing resources at the same time. Modification issue.
package com.test;public class ThreadSafeTest extends Thread implements Runnable { private static int num = 1; public ThreadSafeTest(String name) { setNa me(name); } public void run() { sell(getName()); } private synchronized static void sell(String name){ if (num > 0) { System. out.println(name + ": The number of detection votes is greater than 0"); System. out.println(name + ": /t is collecting payment (about 5 seconds Completed). . . " ); try { Thread. sleep(5000); System. out.println(name + ": /tPrint the ticket, ticket sales are completed" ); num--; printNumInfo(); } catch (InterruptedException e ) { e.printStackTrace(); } } else { System. out.println(name+": No tickets, stop ticket sales" ); } } private static void printNumInfo() { System. out.println("System: Current vote count :" + num); if (num < 0) { System. out.println("Warning: votes are lower than 0, negative numbers appear" ); } } public static void main(String args[]) { try { new ThreadSafeTest( "Ticket Seller Li XX" ).start(); Thread. sleep(2000); new ThreadSafeTest("Ticket Seller Wang X" ).start(); } catch (InterruptedException e) { e.printStackTrace(); } }} Make the program as above to get the run result:
Ticket Conductor Li XX: The number of test tickets is greater than 0 Ticket Conductor Li XX: The payment is being collected (completed in about 5 seconds). . . Ticket Seller Li XX: Print the ticket, ticket sales completion system: Current number of tickets: 0 Ticket Seller Wang X: No tickets, stop ticket sales
A static modifier is added to the sell() method, so that the lock's object becomes a class. When an instance of the class operates on a shared variable, other instances of the class will block its operation on it. This will get the results we want as expected.
Summarize:
1. There are two uses of synchronized keywords: synchronized method and synchronized block.
2. In Java, it is not only a class instance, but each class can also correspond to a lock. When using the synchronized keyword, there are the following points to be paid attention to:
1. The synchronized keyword cannot be inherited. Although synchronized can be used to define methods, synchronized does not belong to a part of the method definition, so the synchronized keyword cannot be inherited. If a method in the parent class uses the synchronized keyword and the subclass also overrides this method, by default, this method in the subclass is not synchronized, and it must be displayed to add the method in the subclass. Only synchronized keywords can be used. Of course, you can also call the corresponding methods in the parent class in the subclass. In this way, although the methods in the subclass are not synchronized, the subclass calls the synchronous methods in the parent class, which is equivalent to the subclass methods being synchronized. like,
Add synchronized keyword to the subclass:
class Parent { public synchronized void method() { } } class Child extends Parent { public synchronized void method () { } } Call the parent class method:
class Parent { public synchronized void method() { } } class Child extends Parent { public void method() { super.method(); } } 2. The synchronized keyword cannot be used when defining interface method.
3. The constructor cannot use the synchronized keyword, but the synchronized block can be used for synchronization.
4. The synchronized position can be placed freely, but cannot be placed behind the return type of the method.
5. The synchronized keyword cannot be used to synchronize variables, such as the following code is incorrect:
public synchronized int n = 0; public static synchronized int n = 0;
6. Although using synchronized keyword is the safest synchronization method, if used in large quantities, it will also cause unnecessary resource consumption and performance losses. On the surface, synchronized locks a method, but in fact it locks a class. For example, the synchronized keyword is used for both non-static methods method1() and method2(). When executing one of the methods , another method cannot be executed. Static methods are similar to non-static methods. However, static methods and non-static methods will not affect each other, see the following code:
public class MyThread1 extends Thread { public String methodName ; public static void method(String s) { System. out .println(s); while (true ); } pub lic synchronized void method1() { method( "non-static method1 method" ); } public synchronized void method2() { method( "non-static method2 method" ); } public static synchronized void method3() { method( "static method3 method" ) ; } public static synchronized void method4() { method ( "static method4 method" ); } public void run() { try { getClass().getMethod( methodName ).invoke( this); } catch (Exception e) { } } public static vo id 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:
Non-static method1 method static method3 method
From the above run results, we can see that method2 and method4 will not run until method1 and method3 are completed. Therefore, we can draw a conclusion that if we use synchronized to define non-static methods in the class, it will affect all synchronized defined non-static methods in this class; if the static method is defined, it will affect all the following methods in this class static method defined by synchronized. This is a bit like a table lock in a data table. When a record is modified, the system locks the entire table. Therefore, the heavy use of this synchronization method will greatly reduce the performance of the program.
Tips for safer access to shared resources:
1. Define the instance variable of private + its get method, instead of defining the instance variable of public/protected. If a variable is defined as public, the object can directly obtain it bypass the control of the synchronization method in the outside world and change it. This is also one of the standard implementations of JavaBean.
2. If the instance variable is an object, such as an array or an ArrayList, then the above method is still unsafe, because when the outside world gets the reference to the instance object through the get method and points it to another object, then the private variable is also It changed, wouldn’t it be very dangerous? At this time, you need to add synchronized to get method and only return clone() of this private object. In this way, what the caller gets is just a reference to the object copy.
Three ways to get object monitor (lock) and notify()
In a thread method, calls to wait() and notify() must specify an Object object, and the thread must have the monitor of the Object object. The easiest way to get the object monitor is to use the synchronized keyword on the object. After calling the wait() method, the thread will release the object lock and enter sleep state. When other threads call the notify() method, the same Object object must be used. After the notify() method is called successfully, the corresponding wait thread on the object will be awakened.
For multiple methods locked by an object, one of them will be selected to wake up when calling the notify() method, and notifyAll() will wake 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 Wa itAndNotifyJFrame 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(ActionEven t event) { if (t == null) { t = new WaitAndNotifyThread(WaitAndNotifyJFrame.this); t.start(); } else if (t .isWait ) { t. isWait = false ; tn(); // 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 Wai tAndNotifyJFrame 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(1 00); if (isWait ) wait(); } } } catch (Exception e) { } } public void n() { synchronized (this ) { notify(); } }} As in the code in the above example box, if the synchronous code block is removed, the execution will throw a java.lang.IllegalMonitorStateException exception.
Looking at the JDK, we can see that the reason for this exception is that the current thread is not the owner of this object monitor.
This method should only be called by a thread that is the owner of this object monitor. In one of three methods, the thread can be made the owner of this object monitor:
1. By executing the synchronous 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 objects of Class type, you can execute synchronous static methods of this class.
When calling a static method, we do not necessarily create an instance object. Therefore, this cannot be used to synchronize static methods, so the Class object must be used to synchronize static methods. Since the notify() method is not a static method, we cannot set the n() method to be static, so we use another example to illustrate :
public class SynchronizedStatic implements Runnable { private static boolean flag = true;//Class object synchronization method one: // Pay attention to the synchronization method of static modification, monitor: SynchronizedStatic.class pri vate 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 uses the obtain class as the monitor. It's the same as the static synchronized method implicitly gets the class monitor. synchronized (SynchronizedStatic. class) { for (int i = 0; i < 100; i++) { try { Thread. sleep(100); } catch (InterruptedException e) { e.printStackTra ce(); } System. out.println( "testSyncBlock:" + i); } } } public void run() { // flag is a variable of static. Therefore, different threads will execute different methods, and only in this way can you see different locking effects. if (flag ) { flag = false ; testSyncMethod(); } else { flag = true ; testSyncBlock(); } } public static void main(String[] args) { ExecutorServi ce exec = Executors. newFixedThreadPool(2); SynchronizedStatic rt = new SynchronizedStatic(); SynchronizedStatic rt1 = new SynchronizedStatic(); exec.execute(rt); exec.execute(rt1); exec.shutdown(); }} The above code runs the result of the running of two synchronization methods to print 100 numbers from 0 to 99 at the same time. Method one is a static synchronization method, and its scope is a class; Method two displays declare the scope of the code block. It's a class. The two methods are similar. Since the scope of method one and method two are both classes, they are mutually exclusive. That is to say, when a thread calls one of these two methods, the remaining uncalled methods will also be used for the others. Threads form blocking. Therefore, the running result of the program will be:
testSyncMethod:0testSyncMethod:1... ...testSyncMethod:99testSyncBlock:0... ...testSyncBlock:99
However, if we replace SynchronizedStatic. class in method 2 with this, due to the lack of scope, these two methods will not form mutual exclusion, and the output results of the program will also be alternately performed, as shown below:
testSyncBlock:0testSyncMethod:0testSyncBlock:1testSyncMethod:1... ...testSyncMethod:99testSyncBlock:99
There are two scopes of locks, one is the object of the class and 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 synchronization can be completed between different objects of the same class.
To summarize the above, the following points need to be noted:
1. Wait(), notify(), and notifyAll() all need to be executed under the premise of having the object monitor, otherwise a java.lang.IllegalMonitorStateException will be thrown.
2. Multiple threads can wait on one object at the same time.
3. Notify() is to randomly wake up a thread waiting on the object. If there is no thread waiting, it will do nothing.
4. The thread that is awakened by notify() does not wake up immediately after notify() is executed, but only after the notify() thread releases the object monitor.
5. These methods of Object are still far from Thread's sleep and interrupt methods, so don't confuse them.