I have been studying internals of Java for quite some time. I am curious to learn and understand how threading/locking takes place in Java.
So, in order to access a synchronized method or a synchronized block, the thread has to acquire the lock on the object first. So, now, here is what I need a bit more light.
So, whenever the thread acquires the lock on the object, does it increment the value of the semaphore internally?
If the answer is yes then let's take a look at this scenario.
class ABC{
public void method_1(){
synchronized(xyz){
....
}
}
public void method_2(){
...
synchronized(xyz){
....
}
}
}
So, say there are two threads: Threaad 1 and Thread 2. Assuming, Thread1 entered method_1 first and therefore acquired a lock on xyz first. And, say now, Thread2 enters method_2 and tries to acquire lock on xyz. What will happen?
(Acc to me, Thread2 will get blocked since it finds that the object's semaphore value>0)
Let me know if my reasoning is correct.
whenever the thread acquires the lock on the object, does it increment the value of the semaphore internally?
Implementation specific, but unlikely, Since every lock can be obtained only once, there is no need for a counter. A simple toggle will do. I assume that every lock holds a reference to the Thread that owns it (or null).
Update: Actually, it is quite a bit more complex than that. The lock also needs to maintain a list of threads that are waiting for it. Also, a thread can temporarily release a lock via the wait/notify mechanism (so there will be an entry counter after all). On top of that, lock management has a big impact on performance, so that there are all kinds of optimizations going on. I found this interesting blog by someone who is working on JVM locking.
So, say there are two threads: Threaad 1 and Thread 2. Assuming, Thread1 entered method_1 first and therefore acquired a lock on xyz first. And, say now, Thread2 enters method_2 and tries to acquire lock on xyz. What will happen?
Yes, Thread 2 will be blocked, and wait until it can eventually obtain the lock.
Your reasoning is roughly correct. Thread 2 will be blocked, and will remain blocked until (at least) Thread1 releases the mutex.
However, the lock is generally not implemented using a conventional semaphore with a simple counter. Typically there is a single lock bit that only gets "inflated" into a full lock if the object is locked reentrantly (e.g. if Thread1 tries to lock xyz while it already holds the lock on that object) or when there is contention for the lock (e.g. when Thread2 tries to lock xyz while Thread1 has it locked).
But you don't need to concern yourself with the implementation details of Java locks ... unless you are implementing a JVM yourself!
The other answers have pretty much answered your question, but for further reading I recommend:
Java Concurrency In Practice
Related
This question already has answers here:
What is the Re-entrant lock and concept in general?
(4 answers)
Closed 4 years ago.
So , a reentrant lock increments the count by one if the current thread acquires the lock again. what i am unable to understand is why and how that helps or benefits us?
The reason, that a reentrant lock is doing this, is to not lock the same Thread again, that has already acquired this lock.
For example: Let's say you have Thread A that is acquiring your Reentrant Lock A. Than Thread B tries to acquire the Lock A, which will result in Thread B getting blocked (more about the Thread states can be found here). Now, Thread A is trying to (again) acquire the Lock A.
Because the Reentrant lock is now highering its count, Thread A is not getting blocked. Thread A still has access over the Lock and can continue (the locks stores the informations about the depth). If he (sooner or later) releases the Lock, the count will be lowered again, to check if Thread A still need the Lock or not. If the count gets to 0, meaning Thread A has released for every time he has acquired, Thread B will get the access over the Lock.
Without reentrancy, you now would have a deadlock. Why? Because Thread A has the lock and would wait to get it again.
Concurrency can be realy complicated, reentrancy helps (just a bit) reducing this complexity.
This helps for the unusual situation when you want to call another method that also requires a lock.
ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
// Something.
} finally {
lock.unlock();
}
}
public void somethingElse () {
lock.lock();
try {
// Something else.
// We can now call another locking method without risking my lock being released.
doSomething();
} finally {
lock.unlock();
}
}
Here public can call doSomething and it will acquire the lock, do it's thing and then release the lock when unlock is called.
However, when somethingElse is called and it calls doSomething it just increases the lock count. When doSomething unlocks it does not release the lock, it merely counts the lock-count down, leaving the final unlock in somethingElse to release the lock.
The point of incrementing a count for the lock is to keep track of how many times the thread acquired the lock, so that the lock won't actually be released until the thread indicates readiness to release the lock the same number of times.
The assumption is that commands for locking will be matched with commands for releasing the lock.
Say my code enters Code Section A, which requires the lock.
Then without exiting Code Section A, it enters Code Section B which also requires the same lock. As you note we have the lock, so we needn't block.
But we'll leave Section B, and Section B is written to release the lock when we exit it. (Otherwise code that reaches Section B without already having the lock would never release the lock.)
We're still in Section A, though, so we don't want to really give up the lock yet, or another thread could take it while we're in Section A.
So when we entered Section B we incremented the lock count, which means when we exit Section B we can reduce the count by one and, seeing that it's not back to 0, not release it.
Then when Section A again releases the lock, the count falls back to 0 and that is when we really let the lock go.
I'm trying to get a better grasp on using sleep function calls in relation to mutex locks. If a mutex were to be held by one thread while the sleep from another thread expires, does control go back to the first thread immediately, or would the thread have to at least wait for the mutex being held to be released first before switching back to the other thread?
There is no relationship between Thread.sleep() and any kind of lock.
A call to Thread.sleep(nnn) returns after at least nnn milliseconds have elapesed (unless it throws an InterruptedException), and that's all a developer ever needs to know about it.
Pretty much all you need to know about mutexes (e.g., synchronized blocks), is that only one thread can lock the same instance at the same time time. A locked mutex becomes available to waiting threads as soon as the owner unlocks it. It has nothing to do with whether or not any of the threads is or was in a sleep() call.
I am investigating Java concurrency and I've found one interesting question which I cannot answer.
For example, I have three threads: ThreadA, ThreadB and ThreadC. ThreadA enters the monitor, and invokes method wait(). Then ThreadB enters the same monitor, invokes method notify() and continue owning the monitor during some period of time. While ThreadB is owning the monitor, ThreadC tries to acquire the monitor too. My question is whether ThreadC can acquire the monitor earlier then ThreadA when ThreadB release it or not? If it can, why? Which conditions should be followed to reproduce it?
As per the Javadoc on Object.notify():
The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
So there exists the possibility that ThreadC owns the monitor before ThreadA. There is no defined order in which any of the threads enter/obtain the monitor, nor is there any priority or fairness mechanism in place for standard synchronization. All it really guarantees is that for a given lock object, only on thread will be in the synchronized block at once.
Given this fact, careful design considerations should go into how threads obtain the lock and for how long. A thread that repeatedly attempts to acquire a lock (acquire and then release but then acquire again) can cause another thread to be locked out indefinitely (called thread starvation).
Using ReentrantLock with a fairness policy can partially overcome this issue at some performance cost (its slightly slower than traditional synchronization).
In my program i am using a Condition object created from a
private static final Lock lock = new ReentrantLock();
like so:
private static final Condition operationFinished = MyClass.lock.newCondition();
Occasionally (as it is always happening with concurrency problems) i encounter following behavior:
Thread1 aquires the lock
Thread1 calls operationFinished.awaitNanos() - this should suspend Thread1 and release the lock.
Thread2 tries to aquire the same lock, but debugging output shows that Thread1 is still holding the lock!
According to documentation this behavior is impossible, because upon awaitNanos() Thread1 first releases the lock and then suspends.
If it didn't release the lock, then it would not suspend, therefore Thread2 could never even get a possibility to try to get hold on the lock.
Has anybody experienced something similar? This errors happens once in 100 times - but still it indicates that I am either not using the concurrency-utilities in a proper way, or that there is some kind of bug in the java.utils.concurrent.* package (which i doubt).
UPDATE:
In response to Peters answer:
I observe following behavior: Apparently the 2 threads deadlock each other. I can see that Thread2 blocks (waiting for the lock) and at the same time awaitNanos() in Thread1 never times out.
Are you sure that the wait time hasn't finished? If you wait for a short period of time (a few hundred nanoseconds, for example), the wait time could expire before Thread2 can fully start, in which case Thread1 might be reactivated first.
Depending on how you are viewing this information, I have seen many examples of where multiple threads wait() on an object still say they are all holding the same lock. It may be that the stack trace or monitoring is mis-leading.
Say you have thread1 which is holding the lock, but in awaitNanos(), you have Thread2 which is trying to obtain the lock(), but sometimes Thread3 is holding the lock as well....
I would do a jstack -l {pid} to check all the threads which might be holding the lock.
If a lock deadlocks, awaitLock (nor wait()) won't return as it must acquire the lock before doing so. (Unless it is interrupted)
All I want to know is that when a thread enters out of a lock, does it means it "ends" or just that it has finished using that function or code which employed the use of the object whose monitor that particular thread is in?
Just that it has finished using that function or code which employed the use of the object. Such pieces of code are commonly known as critical section(s).
For your general understanding: methods run on threads. So it is possible that one method is being executed by multiple threads at the same time.
Imagine you want to make sure that a method, or part of it, can only be executed by one thread at a time. This is called a critical section.
A critical section in Java can be protected by a lock: implicitly via synchronized or explicitly via java.util.concurrent.locks.
Only one thread at a time can acquire the lock and entering the critical section requires that the lock be acquired first. At the end of the critical section the lock is released and the thread continues running but now without holding that lock.
A thread encountering a lock held by another thread (not necessarily for the same critical section) cannot proceed at that point and must wait. The thread, and other threads waiting on the same lock, will be notified when they can retry to acquire the lock. Again, only one thread will win and the process repeats (unless you have a deadlock for example).