DEV Community

ChelseaLiu0822
ChelseaLiu0822

Posted on

Monitor

Java object header

Image description
Klass word points to the class where the object is stored.

Image description

Each Java object can be associated with a Monitor object. If synchronized is used to lock the object (weight
level), the pointer to the Monitor object is set in the Mark Word of the object header.

Image description

When a thread occupies the lock, the Monitor's Owner will be set to the thread, and there can only be one Owner.
If other threads also execute synchronized, they will enter the EntryList and become Blocked.

lightweight lock

Application scenario: Although an object is accessed by multiple threads, the access times of the multiple threads are staggered (no competition).
contention), then lightweight locks can be used to optimize Lightweight locks are transparent to users, and the syntax is still synchronized

process:

Create a Lock Record object. The stack frame of each thread contains a lock record structure, which can be stored internally.
Mark Word of Lock Object
Let the lock record Object reference point to the lock object, and try to replace the Mark word of Object with cas, so that
The value of Mark word is stored in the lock record
If the cas replacement is successful, the object header stores the lock record address and status 00, indicating that the object is locked by this thread.
If it fails there are two situations:

  1. If other threads already hold the lightweight lock of the Object, it means competition and entry into the expansion process.
  2. If you perform synchronized lock reentry yourself, add another Lock Record as the reentry count

Image description

When exiting the synchronized code block (unlocking), the value of the lock record is null, indicating re-entry. At this time, the lock record is reset.
record, indicating that the re-entry count is decremented by one
When exiting the synchronized code block (when unlocking), the value of the lock record is not null. At this time, use cas to change the Mark
The value of Word is restored to the object header
If successful, the unlocking is successful.
Failure, indicating that the lightweight lock has undergone lock expansion or has been upgraded to a weight lock, and the weight lock unlocking process is entered.

expansion lock

If the CAS operation fails when trying to add a lightweight lock, one situation is that other threads are trying to add lightweight locks.
This object has a lightweight lock (with competition). In this case, lock expansion is required to turn the lightweight lock into a heavy lock.

Image description
When Thread-1 performs lightweight locking, Thread-0 has already added a lightweight lock to the lock.
At this time, Thread-1 fails to add a lightweight lock and enters the expansion lock process:
Apply for a Monitor lock for the Object object and let the Object point to the weight lock address.
Then let yourself enter the EntryList and Blocked state of the Monitor
When Thread-0 exits the sync block and is unlocked, using cas to restore the Mark word value to the object header will fail. this
will enter the heavyweight lock unlocking process, that is, find the Monitor object according to the Monitor address, and set the Owner to
null, wake up the Blocked thread in EntryList

Spin optimization

When competing for weight locks, you can also use spin for optimization. If the current thread spins successfully (that is, the lock is held at this time)
The thread has exited the synchronization block and released the lock), then the current thread can avoid blocking
It will increase or decrease the number of spins based on whether the last spin was successful, which is more intelligent.

bias lock

When there is no competition for lightweight locks, CAS operations still need to be performed every time the lock is re-entered.
Java 6 introduced biased locking for further optimization. As long as the CAS thread ID is set to the object for the first time,
Mark Word header, and later found that the ID of this thread is itself, which means there is no competition, and there is no need to re-CAS. In the future
As long as no competition occurs, this object is owned by the thread.

When an object is created:

If the bias lock is enabled (enabled by default), then after the object is created, the markword value is 0x05, which is the last 3 digits.

  1. At this time, his thread, epoch, and age are all 0. Bias lock is delayed by default and will not take effect immediately when the program starts. If you want to avoid delay, you can add VM parameters. Number: -XX:BiasedLockinStartupDelay=0 to tighten the delay If the bias lock is not turned on, then after the object is created, the markword value is 0x01 and the last three digits are 001. At this time Its hashcode and age are both 0. The first 54 bits of markword will be assigned when the hashcode is used for the first time. Thread id -XX:-UseBiasedLocking disables biased locking The order of using locks: give priority to bias locks, then light locks, and finally heavy locks. The hashcode() method disables the biased lock because the biased lock does not have enough space to store the hashcode (requires 31 bit) When other threads use the biased lock object, the biased lock will be upgraded to a lightweight lock. The bias lock will also be revoked when wait/notify is called. These two methods are only available for weight locks, so the lock will be upgraded to weight lock

Batch weight bias

If the object is accessed by multiple threads but there is no competition, the object that is biased towards thread T1 will still have a chance to be accessed again.
Bias T2, heavy bias will reset the Thread ID of the object
When the biased lock cancellation threshold exceeds 20 times, the jvm will redirect these objects to the locking thread when accelerating.

Batch undo

When the biased lock threshold is revoked more than 40 times, all objects of the entire class will become non-biasable, and the newly created Objects cannot be biased either

lock elimination

The JIT just-in-time compiler will optimize the bytecode file. When the lock object is a local variable and cannot escape, it will be automatically eliminated.
This lock achieves the purpose of optimization

Wait and notify

If the owner thread condition is not satisfied, call the wait method to enter WaitSet and change to WAITING state.
After the Owner thread calls notify or notifyAll to wake up, the thread enters the EntryList to compete again and will not immediately
get lock wait (long timeout) has a time limit to wait. It will wake up automatically when the time is up and can be woken up in advance by other threads.

The difference between Sleep and Wait

  1. sleep is a Thread method, and wait is an Object method.
  2. Sleep does not need to be used in conjunction with synchronized, but wait needs to be used in conjunction with synchronized.
  3. sleep will not release the object lock while sleeping, but wait will release the object lock while waiting.
  4. The thread status of sleep and wait is the same, both are TIMED WAITING. notify will only randomly wake up the threads in wait, which will cause false wake-ups.

protective pause mode

Application scenario: One thread waits for the execution result of another thread
There is a result that needs to be passed from one thread to another thread, and let them associate a GuardedObject
If there are results constantly flowing from one thread to another, you can use message queue (producer/consumer)
In JDK, the implementation of join and the implementation of Future adopt this mode.
Because it has to wait for the results from the other party, it is classified into synchronous mode.

Image description

join principle

Is achieved through protective pause mode

Image description

Protected Suspend Mode - Extended

Image description
The Futures in the picture are like the mailboxes on the first floor of a residential building (each mailbox has a room number), t0, t2, and t4 on the left are like residents waiting for mail, and t1, t3, and t5 on the right are like postmen.
If you need to use GuardedObject objects between multiple classes, it is not very convenient to pass them as parameters, so design an intermediate class for decoupling. This will not only decouple the [result waiter] and [result producer], but also decouple them at the same time. Supports the management of multiple tasks.

Asynchronous mode-producer/consumer mode

There is no need to produce results and consume results in a thread-to-thread correspondence.
The consumption queue can be used to balance the resources of production and consumption threads
The producer is only responsible for producing results and does not care how the data is processed. The consumer only processes the data.
The message queue has a capacity limit. No more data will be added when it is full, and no more data will be consumed when it is empty.
Various blocking queues in JDK adopt this model.

Image description

Park & Unpark

Is a method in the LockSupport class
Compare with Object’s wait & notify
wait, notify and notifyAll must be used together with Object Monitor, but park and unpark are not required.
Park unpark blocks and wakes up in thread units, while notify wakes up a waiting thread randomly.
Park unpark can be unparked first, but wait and notify cannot be notified first.
Each thread has a Parker object, consisting of _counter, _cond and _mutex
_counter is used to determine whether the thread needs to rest. _cond is used to store the resting thread.
When _counter = 0, it means a break is needed. _counter will change to 1 after calling unpark.

Image description

Image description

Image description

Thread status switching:

  1. NEW -> RUNNABLE: t.start() method
  2. RUNNABLE <——> WAITING: Call obj.wait() method t thread from RUNNABLE - -> WAITING

When calling obj.notify(), obj.notifyAll(), t.interrupt()
The competition is successful, t thread goes from WAITING - -> RUNNABLE
Competition failed, t thread went from WAITING - -> BLOCKED
The current thread calls the t.join() method. The current thread starts from RUNNABLE - -> WAITING

  1. RUNNABLE <- ->TIMED_WAITING The thread t ends or the interrupt() method of the current thread is called. The current thread starts from WAITING - ->RUNNABLE When the current thread calls the park method, the current thread will change from RUNNABLE - -> WAITING. Calling unpark (target thread) or the thread's interrupt() method will cause the target thread to start from WAITING - -> RUNNABLE
  2. RUNNABLE <- -> BLOCKED If the competition fails when the t thread acquires the object lock using synchronized (obj), it will start from RUNNABLE - -> BLOCKED After the thread synchronization code block holding the obj lock is executed, all BLOCKED threads on the object will be awakened to compete again. After the competition is successful, the cover thread will change from BLOCKED - -> RUNNABLE and other threads will still be BLOCKED.
  3. RUNNABLE <- -> TERMINATED When all codes of the thread have finished running, enter TERMINATED

multiple locks

Subdivide the lock granularity
Benefits: Can enhance concurrency
Disadvantages: If a thread requires colleagues to obtain multiple locks, deadlocks may easily occur.

deadlock

Thread t1 acquires the A object lock, and then wants to acquire the B object lock.
Thread t2 acquires the B object lock, and then wants to acquire the A object lock.
To monitor deadlocks, you can use the jconsole tool, or use jps to locate the process ID, and then use jstack to locate the deadlock.

livelock

The two threads change the end conditions of the object to each other, and neither one can end it in the end.

Hunger problem:

Because the priority of a thread is too low, it is never scheduled to be executed by the CPU and cannot be terminated.
Sequential locking can solve the deadlock problem, but it will cause starvation problems, which can be solved with ReentrantLock.

ReentrantLock

For synchronized, it has the following characteristics:
Interruptible
Timeout can be set
Can be set to fair lock
Support multiple condition variables
Like synchronized, both support reentry.
To create a ReentrantLock object first

Image description

Can be interrupted:
While t1 is waiting, other threads can use the interrupt() method to interrupt the waiting of the t1 thread.
The lock() method is uninterruptible, and the lockInteruptibly() method can be interrupted.

lock timeout

If you still cannot obtain the lock after waiting for a period of time, give up waiting.
The tryLock() method returns a boolean value, false indicates that the lock is not acquired, and it also supports interrupting

fair lock

ReentrantLock is unfair by default (released locks are not allocated in the order of the blocking queue). The original intention is that
In order to solve the starvation problem, pass true during construction to become a fair lock. Fair lock will reduce concurrency.

condition variable

When the conditions are not met, enter waitSet to wait.
ReentrantLock supports multiple condition variables. Different conditions have different waitSets (lounges). You can follow
waitSet(lounge) to wake up
Create a condition variable:
Condition cond1 = lock.newCondition()
Need to obtain the lock before await
cond1.await() enters waiting
cond1.singal() wakes up a thread in cond1
cond1.singalAll() wakes up all threads in cond1

Top comments (0)