synchronized
Synchronized is a keyword used in Java to ensure that only one thread can operate on a shared resource at a time, thereby ensuring that the shared resource is thread-safe.
- Synchronized is a reentrant, unfair, and exclusive pessimistic lock. Synchronized can be classified into biased lock, lightweight lock, and heavyweight lock states. Reentrant is when a thread requests a synchronized method or block of code on an object lock it holds again. Instead of being fair, there is no guarantee of the order in which the waiting thread will acquire the lock. Pessimistic means that every time you try to fetch the data, you think someone else will change it, so the lock will be locked every time you read or write the data. In JDK1.6, we added adaptive CAS spin, lock elimination, lock coarser, biased lock, lightweight lock, and heavyweight lock optimization strategies. The lock can be upgraded from biased lock to lightweight lock, and then to heavyweight lock.
In a concurrent program, the sharing of resources between threads has thread-safety problems, which can be caused by the following two reasons:
- Multiple threads share resources
- Multiple threads work together on a shared resource
Synchronized range
- Synchronized can be used primarily on methods (instance methods, static methods) and code blocks. The main thing to note here is that if the object being locked is a class object of a class, even though you can create multiple instances of it with the new keyword, they still belong to the same class, so they’ll still be locked. That is, when the locked object is a class object of a class, there is still a thread synchronization problem between multiple objects, and they are still mutually exclusive (one thread accesses, the other thread waits) to access the synchronized method or block of code.
The details are as follows:
- Synchronized modifies instance methods. The following code tests the difference between synchronized, normal, and asynchronous methods accessing the same object in multiple threads.
package com.gjy.demo.Thread.Synchronized; /** * @author GJY * @date 2021/6/27 14:27 * @version 1.0 * synchronized Public class synchronizedDemo1 implements Runnable {synchronizedDemo1 implements Runnable;} public class synchronizedDemo1 implements Runnable;} Private static int total=0; private static int total=0; Public synchronized void increase(){for (int I = 1; i < 6; I++) {system.out.println (thread.currentthread ().getname ()+"..." +" th "+ I +" th "); try { total=total+1; Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }}} public synchronized void Declare (){synchronized void declare(); System. Out. Println (Thread. CurrentThread (). The getName () + "execute total - 1"); total--; System.out.println(thread.currentThread ().getName()+" Complete total-1 "); } public void simpleMethod(){system.out.println (thread.currentThread ().getName()+ "---- common instance method --"); } @override public void run() {system.out.println (thread.currentThread ().getName()+" Ready to execute, not yet obtained lock "); // Execute the plain method simpleMethod(); Increase (); // Increase (); // After the increase synchronization method is executed, the lock will be released, and threads 1 and 2 will race to get the lock again. System.out.println(thread.currentThread ().getName()+ declare); Println (thread.currentThread ().getName()+" Ready to execute total-1"); declare(); } public static void main(String[] args) throws InterruptedException { synchronizedDemo1 syn = new synchronizedDemo1(); Thread thread1 = new Thread(SYN," Thread 1"); Thread thread2 = new Thread(SYN," Thread 2"); thread1.start(); thread2.start(); }}Copy the code
- The results
Thread 1 is ready to accumulate, thread 2 is ready to accumulate, thread 2 is ready to acquire the lock ---- the common method of the instance object -- thread 2 is ready to accumulate... // Thread 2 gets the lock first by competing with thread 1, so thread 2 enters the increase synchronization method and accumulates... // If thread 2 has access to a synchronized method, thread 1 has access to an asynchronous method, but thread 2 has no access to another synchronized method... The third accumulation thread 2 performs the accumulation operation... The fourth accumulation thread 2 performs the accumulation operation... // Thread 2 will release the lock. Thread 2 is ready to execute total. Thread 1 will execute total. // Then thread 1 obtains the lock and enters the increase synchronization method. The second accumulator thread 1 performs the accumulator operation... The third accumulation thread 1 performs the accumulation operation... The fourth accumulation thread 1 performs the accumulation operation... // Thread 1 releases the lock after the fifth accumulation. Then thread 1 and thread 2 will race for the lock again thread 1 is going to do total-1 thread 2 is going to do total-1 // Thread 2 is going to race to get the lock first enter the declear method and perform total-1 operation thread 2 is going to perform total-1 Thread 1 executes total-1 to completeCopy the code
- As you can see from the example above, every time a thread enters a synchronized modified method or a synchronized wrapped block of code, the current thread is required to hold the object lock. If another object is currently holding the instance object lock, the new thread must wait until the other thread finishes executing the synchronized method.
The characteristics of the synchronized
-
Atomicity. Synchronized can ensure mutually exclusive access to shared resources by multiple threads, and synchronized code can achieve atomicity.
-
Visibility. Synchronized guarantees that changes to a shared resource can be seen in a timely manner. In the Java memory model, the changes to a shared variable must be synchronized to main memory before the unlock operation can be performed. If on a Shared resource lock lock before operation, working memory sharing the value of a variable must be empty (because each thread access to Shared variables are Shared variables in the main memory of a copy, if not empty, data inconsistency occurs, the current thread of Shared variables do not agree with Shared variables in main memory). * When this shared variable is used, it needs to be reloaded from main memory to get the latest value of the shared variable.
-
The differentiation. Synchronized effectively solves the reordering problem: an unlock operation must precede a subsequent lock operation on the same lock, thus ensuring that the shared variable of the main memory value is always up to date.
Synchronized deadlock problem
A deadlock is when two or more threads block waiting to acquire a lock held by another thread in the deadlock state. For example, thread 1 holds lock A and is trying to acquire lock B; If thread 2 holds lock B and is trying to acquire lock A, then the two threads are waiting for each other to acquire the lock that is already held by the other. In this case, thread 1 can never acquire lock B, and thread 2 can never acquire lock A. This is A deadlock.
- The following example
A private static final Object lockA = new Object(); Private static final Object lockB = new Object(); public static void synchronizedDead(){ Thread thread1 = new Thread(new Runnable() { @Override public void run() { // synchronized (lockA) {system.out.println (" thread 1 holds lockA "); B try {thread.sleep (2000); } catch (InterruptedException e) { e.printStackTrace(); } // thread 1 tries to acquire lockB, and thread 1 does not release A synchronized (lockB) {system.out.println (" thread 1 tries to acquire lockB "); }}}}, "thread 1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { synchronized (lockB) { System.out.println(" thread 2 holds lock B"); A try {thread.sleep (20000); A try {thread.sleep (20000); } catch (InterruptedException e) { e.printStackTrace(); } // thread 2 tries to acquire A synchronized (lockA) {system.out.println (" thread 2 tries to acquire lockA "); }}}}, "thread 2"); thread1.start(); thread2.start(); } public static void main(String[] args) { synchronizedDeadDemo.synchronizedDead(); }Copy the code
Implementation principles of synchronized
- Every object in Java has a Monitor object associated with it. When a Monitor object is held, the object is locked and other threads can only block and wait to acquire the lock. Synchronized is a method/block synchronization based on entering and exiting a Monitor object. The code block is locked by adding the monitorenter and Monitorexit directives to the front and back respectively. The method lock is determined by a marker bit. In a multi-threaded environment, if data needs to be shared between threads, the problem of mutually exclusive access to data needs to be solved to ensure thread safety. Monitor can ensure that data is accessed by only one thread at a time. Like all objects, all Java objects have the potential to become Monitor because in Java design, each Java object is created with an invisible lock, which is either an internal lock or a Monitor lock.