Introduction to java locks and java understanding

Source: Internet
Author: User

Introduction to java locks and java understanding

In concurrent programming, multiple threads often access the same shared resource. At this time, developers must consider how to maintain data consistency. in java, the synchronized keyword is often used to maintain data consistency. Synchronized locks the shared resources. Only the threads that obtain the lock can access the shared resources. In this way, the shared resources can be accessed in order, because access to shared resource attributes is necessary and necessary, the following is an example.
I. java locks
In java, locks refer to built-in locks. Every java object can be used as a synchronization lock. Although everything in java is an object, however, the lock must be of the reference type, but not the basic data type. Each referenced object can assume an implicit lock role for synchronization. The execution thread automatically acquires the lock before it enters the synchronized block, the thread Automatically releases the lock when giving up control of the synchronized block, whether it exits through a normal statement or throws an exception during execution. The only way to obtain the lock is to enter the synchronization block or method protected by the internal lock.
As mentioned in the introduction, access to shared resources must be sequential. That is to say, when multiple threads access shared resources, only one thread can obtain the lock of the shared resource, when thread A tries to obtain the Lock of thread B, thread A must wait or block until thread B releases the lock. Otherwise, thread A will wait, therefore, java built-in locks are also called mutex locks, that is, locks are actually a mutex mechanism.
According to different usage methods, the locks are generally divided into Object locks and class locks. The two locks are very different. The Object locks are applied to the instance method or an object instance, the Class lock applies to static methods or Class objects. A Class can have multiple instance objects, so the object lock of a Class may have multiple, but each Class has only one Class object, so there is only one Class lock. A class lock is only a conceptual concept. It does not actually exist. It is only used to help us understand the differences between the instance method and the static method.
In java, the Lock mechanism is not only limited to the use of the synchronized keyword, but also the Lock provided after JDK. Lock is not covered in this article. A synchronized block consists of two parts: the reference of the lock object and the block protected by the lock. If the lock is applied to the instance method, the lock is the current object of the method. The static synchronized method acquires the lock from the Class object.
Ii. Example of synchronized
1. Multi-Window ticket sales
Assume that a ticket sales system has several windows to sell tickets at the same time. It is clear that tickets exist as resources shared among multiple windows. Because the seat number is determined, therefore, the number above the ticket is also determined. We use multiple threads to simulate tickets for multiple windows at the same time. First, we will test the ticket sales without using the synchronized keyword.
First, place the ticket itself as a shared resource in a separate thread. The thread that exists as a shared resource should obviously implement the Runnable interface, we pass in the total number of tickets num as an input parameter. After a ticket is generated each time, we perform the num subtraction operation until num is 0, which indicates that the ticket has been sold out, enable multiple threads to pass in the ticket resource.

Public class Ticket implements Runnable {private int num; // Number of votes private boolean flag = true; // if it is false, the Ticket sales stop public Ticket (int num) {this. num = num ;}@ Override public void run () {while (flag) {ticket () ;}} private void ticket () {if (num <= 0) {flag = false; return;} try {Thread. sleep (20); // simulate a delayed operation} catch (InterruptedException e) {e. printStackTrace ();} // output the current window number and the ticketing serial number System. out. println (Thread. currentThread (). getName () + "sold Ticket serial number:" + num --) ;}} public class MainTest {public static void main (String [] args) {Ticketticket = new Ticket (5 ); threadwindow01 = new Thread (ticket, "window 01"); Threadwindow02 = new Thread (ticket, "window 02"); Threadwindow03 = new Thread (ticket, "window 03 "); window01.start (); window02.start (); window03.start ();}}

The output result of the program is as follows:
Window 02 sold ticket serial number: 5
Window 03 sold ticket serial number: 4
Window 01 sold ticket serial number: 5
Window 02 sold ticket serial number: 3
Window 01 sold ticket serial number: 2
Window 03 sold ticket serial number: 2
Window 02 sales ticket serial number: 1
Window 03 sold ticket serial number: 0
Window 01 sold ticket serial number:-1
From the program running result above, we can see that not only does the ticket serial number have a duplicate number, but the number of tickets is also incorrect. This ticketing system is much worse than 12306. When people are busy, they just cannot get a ticket, the ticket sales system is better here, with more tickets than expected and the risk of multiple people competing for the same seat. If this problem does not occur in a single ticket window, multiple tickets are sold at the same time, so the competition for shared resources is disordered. It is also very easy to solve this problem, that is, in ticket () add the synchronized keyword before the method or use the synchronized block to completely include the method body of the ticket () method.

// Method 1 private synchronized void ticket () {if (num <= 0) {flag = false; return;} try {Thread. sleep (20); // simulate a delayed operation} catch (InterruptedException e) {e. printStackTrace ();} System. out. println (Thread. currentThread (). getName () + "sold ticket serial number:" + num --);} // method 2 private void ticket () {synchronized (this) {if (num <= 0) {flag = false; return;} try {Thread. sleep (20); // simulate a delayed operation} catch (InterruptedException e) {e. printStackTrace ();} System. out. println (Thread. currentThread (). getName () + "sold ticket serial number:" + num --);}}

Let's take a look at the program running result with the synchronized keyword added:
Window 01 sold ticket serial number: 5
Window 03 sold ticket serial number: 4
Window 03 sold ticket serial number: 3
Window 02 sales ticket serial number: 2
Window 02 sales ticket serial number: 1
From this we can see that the effect of adding the synchronized keyword to the instance method is the same as that of adding synchronized to the entire method body. In addition, it is important to note the locking time. In this example, two operations in the ticket () method are prone to disorder. One is in the if Statement module, and the other is in the num -, these two operations are not atomic operations, but they need to be regarded as a whole operation when running, so synchronized wraps the entire method body together. Otherwise, assume that the current num value is 1, but the window 01 is executed to num-. The entire operation has not been completed, and only the value assignment operation has not been performed, however, window 02 has entered the if Statement module. At this time, num is equal to 1. When window 02 runs the output statement, the num-in window 01 has completed the self-subtraction operation, at this time, window 02 will output the ticket with the serial number 0. If the synchronized keyword is added to the run method, the operation will not be disordered or wrong at this time, but this locking method is tantamount to a single window operation. When the window 01 gets the lock, it enters run () after the method is used, the statement execution will jump out of the loop until the flag is false. At this time, the num value is 0, which means that the ticket has been sold out. This method does not support multithreading, contrary to the original design principle-Multi-Window ticket sales.
2. Lazy Singleton Mode
There are many ways to create the singleton mode. This article only describes how to create a singleton mode. In Android development, the single-sample mode is the most commonly used design mode, because it is easy to operate and can effectively reduce memory overflow. The following is an example of creating a singleton mode in lazy mode:

    public class Singleton {     private static Singletoninstance;     private Singleton() {     }     public static SingletongetInstance() {     if (instance == null) {     instance = new Singleton();     }     return instance;     }    }

If you fully understand the logic of Multi-Window ticket sales, you can see that there is a problem with the implementation method. We can simply create several threads to obtain the hascode value of the output object of the singleton.
Com. sunny. singleton. Singleton @ 15c330aa
Com. sunny. singleton. Singleton @ 15c330aa
Com. sunny. singleton. Singleton @ 41aff40f
Different objects will be found in the multi-threaded mode. This Singleton mode is obviously not what we want, so according to the logic of the above multi-window ticket sales, we are in getInstance () add a synchronized keyword to the method to add a lock to the method. After adding a lock, you can avoid generating multiple different objects in multi-threaded mode, but it also brings a efficiency problem, because no matter which linear entry into the getInstance () method will first obtain the lock and then release the lock again, this is one aspect. The other aspect is that only when the getInstance () method is called for the first time, that is, the multi-thread concurrency problem occurs in the if statement block, but we simply lock the entire method. The discussion raises another question: Is the synchronized Method good or the synchronized code block good? One principle is that the smaller the lock range, the better. The lock aims to use the code to lock the lock as an atomic operation because non-atomic operations are not thread-safe, therefore, the synchronized code block should be a preferred locking method during development.

    public static SingletongetInstance() {     if (instance == null) {     synchronized (Singleton.class) {     instance = new Singleton();     }     }     return instance;    }

Here, we will also encounter A problem similar to the above. Multiple instances will be generated in multi-thread concurrency. For example, both thread A and thread B enter the if statement block. Suppose thread A gets the lock first, thread B waits. After A new instance is created, thread A releases the lock. After thread B acquires the lock, it executes the new statement again, which cannot guarantee the singleton requirement, the following code returns a null check and performs a dual check and lock?

    public static SingletongetInstance() {     if (instance == null) {     synchronized (Singleton.class) {     if(instance==null){     instance = new Singleton();     }     }     }     return instance;    }

This mode is the singleton Mode Implemented by double check locks. At the code level, we have basically ensured thread security, but there is still a problem. The problem of double check locks is: it cannot be guaranteed that it will run smoothly on a single processor or multi-processor computer. The failure of double check locking is not attributed to the Implementation bug in JVM, but to the memory model of java platform. The memory model allows the so-called "unordered write", which is also a major cause of failure of these idioms. For more details, refer to the problem of double check locks in Java Singleton mode. Therefore, we recommend that you use the cool-style or static internal class method to create instances in singleton mode.
3. synchronized does not have inheritance
We can use a simple demo to verify this problem. In a method, a series of numbers are output sequentially, the thread name where the number is located is output, and the synchronized keyword is added to the parent class, to test the method of rewriting the parent class by subclass, add the synchronized keyword and the no-Keyword keyword.

    public class Parent {     public synchronized void test() {     for (int i = 0; i < 5; i++) {     System.out.println("Parent " + Thread.currentThread().getName() + ":" + i);     try {     Thread.sleep(500);     } catch (InterruptedException e) {     e.printStackTrace();     }     }     }    }

Subclass inherits the Parent class Parent and overwrites the test () method.

    public class Child extends Parent {     @Override     public void test() {     for (int i = 0; i < 5; i++) {     System.out.println("Child " + Thread.currentThread().getName() + ":" + i);     try {     Thread.sleep(500);     } catch (InterruptedException e) {     e.printStackTrace();     }     }     }    }

The test code is as follows:

    final Child c = new Child();    new Thread() {     public void run() {     c.test();     };    }.start();    new Thread() {     public void run() {     c.test();     };    }.start();

The output result is as follows:
Parent Thread-0: 0 Child Thread-0: 0
Parent Thread-0: 1 Child Thread-1: 0
Parent Thread-0: 2 Child Thread-0: 1
Parent Thread-0: 3 Child Thread-1: 1
Parent Thread-0: 4 Child Thread-0: 2
Parent Thread-1: 0 Child Thread-1: 2
Parent Thread-1: 1 Child Thread-0: 3
Parent Thread-1: 2 Child Thread-1: 3
Parent Thread-1: 3 Child Thread-0: 4
Parent Thread-1: 4 Child Thread-1: 4
The output information shows that the serial number in a single thread is output in the Parent class to execute the code in another thread. However, Child in the Child class does generate a number alternate between two threads, therefore, synchronized does not have inheritance.
4. deadlock example
Deadlock is a common problem in multi-thread development. If multiple threads access multiple resources and compete with each other, deadlocks may occur. The following is an example of a deadlock. When one thread waits for the lock held by another thread and the other thread is waiting for the lock held by the lock, both threads will be in the blocking status, the program has a deadlock.

    public class Thread01 extends Thread{     private Object resource01;     private Object resource02;     public Thread01(Object resource01, Object resource02) {     this.resource01 = resource01;     this.resource02 = resource02;     }     @Override     public void run() {     synchronized(resource01){     System.out.println("Thread01 locked resource01");     try {     Thread.sleep(500);     } catch (InterruptedException e) {     e.printStackTrace();     }     synchronized (resource02) {     System.out.println("Thread01 locked resource02");     }     }     }    }    public class Thread02 extends Thread{     private Object resource01;     private Object resource02;     public Thread02(Object resource01, Object resource02) {     this.resource01 = resource01;     this.resource02 = resource02;     }     @Override     public void run() {     synchronized(resource02){     System.out.println("Thread02 locked resource02");     try {     Thread.sleep(500);     } catch (InterruptedException e) {     e.printStackTrace();     }     synchronized (resource01) {     System.out.println("Thread02 locked resource01");     }     }     }    }    public class MainTest {     public static void main(String[] args) {     final Object resource01="resource01";     final Object resource02="resource02";     Thread01thread01=new Thread01(resource01, resource02);     Thread02thread02=new Thread02(resource01, resource02);     thread01.start();     thread02.start();     }    }

When the above program is executed, it will keep waiting and there will be a deadlock. After the thread Thread01 acquires the resource01 lock, wait for 500 ms and try to obtain the resource02 lock. However, the resouce02 lock has been held by Thread02, and Thread02 also waits for ms to obtain the resouce01 lock, however, the thread has been held by Thread01, so that both threads are waiting for all resources of the other side, resulting in a deadlock.
Iii. Others
The key word synchronized has the lock re-entry function. When a thread already holds an object lock and requests the object lock again, the lock of the object can be obtained. This method is required, otherwise, there is no way to call another synchronized Method of the object within a synchronized method. A lock reentrant associates a counter with each other and a thread that occupies it. When the counter is 0, it is considered that the lock is not possessed. When a thread requests an unoccupied lock, JVM will record the occupied lock and set the counter to 1. If the same thread requests the lock again, the counter will increase progressively. The counter will decrease every time the occupied thread exits the synchronization code block until the lock is released when it is reduced to 0.
When declaring an object as a lock, pay attention to the string type lock object, because the string has a constant pool, if the lock held by different threads is a string lock with the same character, the two locks are actually the same.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.