When it comes to concurrency, let's talk about Synchronized and ReentrantLock.
What are the main reasons for the thread safety problems?
- There is shared data (also known as critical resources);
- There are multiple threads working together to share data.
The solution to the above problem: when there is only one thread at the same time operating shared data, other threads must wait until the thread finishes processing the data before operating the shared data.
Therefore, mutex is introduced, which has the following features:
- Mutual exclusion
At the same time, only one thread holds an object lock. Through this feature, multi-threaded coordination mechanism can be realized, so that only one thread can access code blocks (composite operations) that need to be synchronized at the same time. Also known as atomicity.
- visibility
Make sure that the changes made to the shared variable before the lock is released are visible to other threads that subsequently acquire the lock (obtain the latest value of the shared variable when acquiring the lock), otherwise, another thread may be caused by continuous operation on a copy of the local cache.
synchronized locks objects, not code.
Heap is the place where threads share and often access operations, so the key to solve the thread problem is to give an appropriate lock to an object.
At the same time, locks are divided into two categories:
- Get object lock
There are two ways to get an object lock:
1. Sync code block
Synchronized (this), synchronized (instance), and what we call "lock" is the instance object in parentheses.
2. Synchronous non static method
synchronized(method), where the lock refers to the instance object of the current object.
Here is a simple demo
public class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { syncObjectBlock1(); } else if (threadName.startsWith("C")) { syncObjectMethod1(); } } /** * Asynchronous method */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Method has synchronized(this|object) {} synchronized code block */ private void syncObjectBlock1() { System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized Modifying non static methods */ private synchronized void syncObjectMethod1() { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }
Operation result:
First, let's see the result of A thread executing async():
Because the async() method itself and its internal properties are not surrounded by synchronized, it can work normally.
Take a look at the execution of thread B:
In the method of B thread execution, if this lock is used, the ones wrapped by this lock will execute in order, and those without this lock will execute asynchronously and normally.
Look at the method of C thread execution. It uses synchronized to decorate the method:
The entire method wrapped in synchronized is accessible to only one thread at a time.
- Get class lock
There are two ways to obtain a class lock:
1. Synchronized code block (class. class)), the lock is the class object (class object) in parentheses ()
2. Synchronized static method. The lock is the class object of the current object
Here is a simple demo:
public class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("D")) { syncClassBlock1(); } else if (threadName.startsWith("E")) { syncClassMethod1(); } } private void syncClassBlock1() { System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } private synchronized static void syncClassMethod1() { System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }
The output results are as follows:
The difference is that only one is modified by static, and one is not modified by static.
It's not hard to find that the execution process is the same as before. First, let's look at thread D: the statement d starts to output is executed asynchronously, so thread 1 and thread 2 print out the statements that are not covered by synchronized, and then obtain locks one by one to print out the corresponding statements in order:
This time, thread E completely covers the lock, and obtains the whole lock in order:
Summarize the object locks and class locks:
- When a thread accesses the synchronized code block of an object, another thread can access the unsynchronized code block of the object;
- If the same object is locked, when a thread accesses the synchronization code block of the object, the thread of the synchronization code block of the other access object will be blocked;
- If the same object is locked, when a thread accesses the synchronization method of the object, the thread of the synchronization method of the other access object will be blocked;
- If the same object is locked, when a thread accesses the synchronization code block of the object, the thread of the synchronization method of another access object will be blocked, and vice versa;
- The locks of different objects of the same class do not interfere with each other;
- Class lock is also a special object lock, so different objects of the same class use class lock synchronously;
- Class locks and object locks do not interfere with each other.