basic
Synchronized. Both mutex and synchronization can be accomplished in Java using the synchronized keyword.
Mutual exclusion is a race condition that guarantees the occurrence of a critical section. Only one thread can execute the critical section code at a time. Synchronization occurs when threads execute in different order, wait for another thread to finish, or use wait/notify.
synchronized(object)// Thread 1, thread 2
{
/ / critical region
}
Copy the code
Synchronized
class Test{
public synchronized void test(a) {}}/ / equivalent to the
class Test{
public void test(a) {
synchronized(this) {}}}class Test{
public synchronized static void test(a) {}}/ / equivalent to the
class Test{
public static void test(a) {
synchronized(Test.class) {
}
}
}
Copy the code
Principle of synchronized
Object head
The object header mainly stores the MarkWord, which stores the hashcode and lock information of the object. In addition to the MarkWord object header, there is Class meta information — a pointer (memory address) to the Class object. If it’s an array, it stores the length of the array.
Instance data The instance data part is the valid information that the object really stores and the contents of various types of fields defined in the program code. Everything that’s inherited from the parent class is recorded.
HotSpot requires that the start and end address (let’s say the size of the object) of an object be an integer multiple of 8, and that the header part of the object be exactly a multiple of 8, so that the instance data part needs to be filled if it is not a multiple of 8
The bytecode
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized(lock) { counter++; }}// The corresponding bytecode
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // Field lock:Ljava/lang/Object; The lock of the reference
3: dup / / copy the lock
4: astore_1 // lock reference -> slot 1
5: monitorenter // Set the Lock object MarkWord as a Monitor pointer
6: getstatic #3 // Field counter:I
9: iconst_1 // Prepare the constant 1
10: iadd // +1
11: putstatic #3 // Field counter:I
14: aload_1 // Get the lock reference from slot 1
15: monitorexit // Wake up EntryList by resetting the Lock object MarkWord
16: goto 24 // Jump to line 24
19: astore_2 // e -> slot2 Abnormal objects are stored in Slot 2
20: aload_1 // Get the lock reference from slot 1
21: monitorexit // Wake up EntryList by resetting the Lock object MarkWord
22: aload_2 // Get slot 2 (e)
23: athrow // throw e
24: return / / return
Exception table:
from to target type
6 16 19 any
19 22 19 any
LineNumberTable:
line 15: 0
line 16: 6
line 17: 14
line 18: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4Copy the code
Synchronized lock management is dependent on the Monitor. When the thread owner of the Monitor has the right to synchronize, the thread enters the Synchronized block and calls monitorenter. When the thread exits the Synchronized block, it calls Monitorexit. Release your holdings of Monitor.
Monitor: The Monitor of an object. Whenever synchronization occurs, the thread creates a Monitor object associated with the current object. Monitor can only be owned by one thread, and the current object is locked.
In the Java virtual machine (HotSpot), Monitor is implemented through ObjectMonitor (c++), which has three important properties
ObjectMonitor() {
_WaitSet = NULL; // Threads in the wait state are added to the _WaitSet
_EntryList = NULL ; // Threads waiting for a lock block are added to the list
_owner = NULL;
}
Copy the code
Synchronized lock escalation process
Biased locking
Bias locking was introduced in Java 6 for further optimization: only the first time CAS was used to set the thread ID to the object’s Mark Word header, and later discovered. If the thread ID is its own, it means that there is no competition and no need to re-cas. In the future, as long as no race occurs, the object is owned by that thread.
Biased lock acquisition and undo logic
(biased_lock:1&&ThreadId is null) (biased_lock:1&&ThreadId is null) (biased_lock:1&&ThreadId is null)
2. If the state is biased, write the current thread ID to MarkWord a via CAS. Says it has won a biased locking, lock object then perform synchronized code block b) if the cas fails, other threads have already obtained the biased locking, this situation is that the current lock there is competition, need to cancel has been biased locking thread, and it holds a lock escalation for lightweight lock (this operation need to wait for global security, That is, no thread is executing bytecode.
3. If it is biased, check whether the ThreadID stored in Markword is equal to the current thread’s ThreadID). If equality does not need to acquire the lock to execute the synchronization block directly. If they are not equal, the CAS replacement is performed and the synchronized code block is successfully executed. If it fails, the current lock is biased to other threads. You need to undo the biased lock and upgrade to a lightweight lock.
undo
Lightweight lock
Lightweight lock locking
The process of upgrading to lightweight locks:
- The thread creates a LockRecord in its stack frame.
- Copy the MarkWord in the lock object’s object header to the thread’s newly created lock record.
- Points the Owner pointer in the lock record to the lock object.
- Replace the MarkWord of the lock object’s object header with a pointer to the lock record.
spinlocks
Lightweight locks use spin locks. Spin means that when another thread is competing for the lock, the thread will wait in place, instead of blocking the thread until the thread that acquired the lock releases the lock, and then the thread can immediately acquire the lock. Note that while the lock loops in place, it consumes CPU, as if it were executing an empty for loop. Therefore, lightweight locks are suitable for situations where synchronized blocks of code execute quickly, so that threads wait in place for a short period of time to acquire the lock. The use of spin-lock, in fact, has a certain probability background, in most of the same step block execution time is very short. So the performance of the lock can be improved by having seemingly uncontested loops. But spin must be conditioned, otherwise if a thread takes a long time to execute a synchronized block of code, the thread’s constant loop will consume CPU resources. The default number of spins is 10, which can be changed with preBlockSpin.
Unlocking lightweight locks
The lock release logic of lightweight locks is actually the reverse logic of lock acquisition. By CAS operation, the LockRecord in the thread stack frame is replaced back to the MarkWord of the lock object. If successful, there is no contention. If it fails, the current lock is in contention, and the lightweight lock expands to become the heavyweight lock.
Weight of the lock
Operating system, see the bytecode above. After you add the synchronized code block, you will see a Monitorenter and a MonitoreXit in the bytecode. Every JAVA object is associated with a monitor, which we can think of as a lock. When a thread wants to execute a synchronized method or block of code, it must first acquire the monitor corresponding to the synchronized object.