In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-21 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly explains "how to understand the lock in programming". The content of the explanation in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought. Let's study and learn how to understand locks in programming.
What kind of existence is a lock?
With the development of business and the increase of the number of users, the problem of high concurrency often becomes a thorny problem that programmers have to face and deal with, and concurrent programming is a relatively advanced and obscure knowledge in the field of programming. It is not so easy to write good concurrent programs if you want to learn concurrency related knowledge well.
Programmers who write Java may be relatively happy at this point, because there are a large number of encapsulated synchronization primitives and master-written synchronization utility classes in Java, which makes it much easier to write correct and efficient concurrent programs.
Although this high degree of encapsulation abstraction simplifies the writing of the program, it hinders our understanding of its internal implementation mechanism. now let's make an analogy from the perspective of locks in the real world. see what kind of locks exist in the program world?
Locks in the program world
If someone asks you, "how to ensure that the house is not entered by strangers"? I think it's easy for you to think, "just lock it!" . And if someone asks you, "how to deal with the concurrency of multiple threads"? I think you might blurt out, "just lock it!" .
Similar scenarios are easy to understand in the real world, but in the programming world, these words are full of confusion. We have seen all kinds of locks in the real world. What does the lock in Java look like?
In the real world, we usually need the key to open the lock and enter the house, so what is the key that opens the lock in the program world? In reality, locks are usually located on doors or cabinets or other locations, so where do locks exist in the program world? We are usually the ones who lock and unlock in the real world, so who is the one who locks and unlocks in the program world?
With these questions in mind, we want to learn more about what kind of locks exist in Java. Where to start to understand, I think the lock is first used in the program, then start from the use of the lock to detect it!
The use of locks
When it comes to locks in Java, there are usually two categories, one is the concurrent synchronization primitive Synchronized provided at the JVM level, and the other is several implementation classes of the Lock interface at the Java API level.
Java API-level locks such as Reentrantlock and ReentrantReadWriteLock have very detailed source code, you can see how they are implemented, you may be able to find the answer above, here we take a look at Synchronized.
Let's take a look at the following code:
Public class LockTest {Object obj=new Object (); public static synchronized void testMethod1 () {/ / synchronize the code. } public synchronized void testMethod2 () {/ / synchronization code} public void testMethod3 () {synchronized (obj) {/ / synchronization code}
Many books on concurrent programming summarize the use of Synchronized as follows:
When Synchronized modifies a static method (corresponding to testMethod1), it locks the class object of the current class, which is the corresponding LockTest.class_ object _.
When Synchronized modifies the instance method (corresponding to testMethod2), it locks the object of the current class instance, which corresponds to the this reference _ object _ in LocKTest.
When Synchronized modifies the synchronous code block (corresponding to testMethod3), it locks the object instance in parentheses in the synchronous code block, which is the corresponding obj_ object _.
From here we can see that the use of Synchronized depends on a specific object, from which we can find that there is some association between the lock and the object. So the next step is to see what clues there are about the lock in the object.
The composition of an object
Everything is an object in Java, just like your object has long hair and big eyes (maybe everything is just imagination). Objects in Java consist of three parts. They are object header, instance data, alignment padding.
The instance data is easy to understand, which is the space occupied by the field data that we define in the class. The reason for alignment padding is that Java-specific virtual machines require that the size of an object must be an integral multiple of 8 bytes. If the storage space occupied by an object lock ends up with a fragment of less than 8 bytes, fill it to 8 bytes. It seems that the lock will not have much to do with either of these areas, so the lock should have some relationship with the object header, as shown in the following figure:
Object composition .png
Let's take a look at what's in the object's header:
Let's take a 32-bit virtual machine as an example (64-bit analogy). The Mark Word has only four bytes and has to store information such as HashCode. Can the lock be realized completely within these four bytes? This sentence is completely wrong before Jdk1.6 and is true in some cases after Jdk1.6.
Why would you say that?
This is because the threads in Java correspond to the local operating system threads one by one, and the operating system divides the system space into user mode and kernel state in order to protect the internal security of the system, prevent some random calls of internal instructions, and ensure the security of the kernel. The threads we usually run are only running in user mode, when we need to call operating system services (called system calls here), such as read. When operating such as writer, there is no way to initiate a call directly in the user mode, so it is necessary to switch between the user mode and the kernel state.
The reason why Synchronized is called heavyweight lock in the early days is that locking and unlocking with Synchronized requires switching between user mode and kernel mode, so early Synchronized is a heavyweight lock, which requires blocking and awakening of threads, dequeuing and queuing of blocking queues and conditional queues, and so on, which we will talk about later, which are obviously impossible to store within these four bytes. But Jdk1.6 made a series of optimizations to Synchronized, including lock upgrades, which made this statement partly true.
Lock upgrade process
The reason why the previous sentence is true in some cases is that during Jdk1.6, the virtual machine team made a series of optimizations to Synchronized, which we will not discuss, which are documented in many books on concurrent programming. What we want to talk about here is one of the important optimizations-lock upgrade.
The lock upgrade process of Synchronized in Java is as follows: no lock-- > bias lock-- > lightweight lock-- > heavyweight mutex.
That is, unless there is serious lock contention between multiple threads, Synchronized will not use the heavy mutexes that Jdk1.6 used to be.
We know that in the real world, we are responsible for locking and unlocking, so in the program world, threads actually play the role of human to lock and unlock.
Bias lock
At the beginning, when it was unlocked, we could understand that the door of the treasure house was unlocked, and then the first thread ran into the synchronous code area (the first person came to the door), adding a biased lock. What is the lock in this time? This time is actually similar to the shape of a face recognition lock, the first thread entering the synchronization code block itself as a key, will be able to uniquely identify a thread ID saved to the Mark Word.
The contents of the Mark Word at this time are as follows:
Biased lock. Jpg
The Epoch of the 23 bits of the four bytes used to store the thread ID,2 bit of the first thread that acquired the biased lock represents the validity of the biased lock, the 4-bit object generation age, whether 1 bit is the biased lock (1 is yes), and the 2-bit lock flag bit (01 is the biased lock).
When the first thread runs to the synchronous code block, it checks the object header of the object used by the Synchronized lock. If the thread header of one of the three objects used by Synchronized mentioned above is empty, and the bias lock is valid, it means that the object header is still unlocked (that is, the treasure house is not locked yet). Then the first thread will use CAS to replace its own thread ID with the thread ID of the object header Mark Word. If the replacement is successful, it means that the thread has acquired the bias lock, then the thread can safely execute the synchronization code. Later, if the thread enters the synchronization code again, during this period, if other threads do not acquire the bias lock You only need to simply compare whether your thread ID is consistent with the thread ID in Mark Word, and if it is consistent, you can go directly to the synchronous code area, so the performance loss is much less.
Biased locks are based on the fact that HotSpot's R & D team has done a study and shown that locks usually do not compete and are always acquired by the same thread multiple times.
On the contrary, if this situation is not very common, that is, there is a lot of competition for locks, or usually locks are acquired by multiple threads in turn, it is useless to favor locks.
Lightweight lock
From here, we can see how the lock exists when the lock is biased at the beginning. As we said earlier, the biased lock exists when there are no multiple threads competing for the lock. However, competitive locking is inevitable in a high concurrency environment, and Synchronized opens his way to promotion.
When there are multiple threads competing for locks, the simple bias lock is not so safe and cannot be locked, so it is necessary to change the lock and upgrade to a more secure lock. At this time, the lock upgrade process can be divided into two steps: (1) partial lock revocation and (2) lightweight lock upgrade.
First of all, how to undo the biased lock? we say that the lock biased to the lock is actually the thread ID in Mark Work. At this time, changing the Mark Word is naturally equivalent to revoking the biased lock. Then the question is that the lock is biased to use the thread ID to indicate, what should be used for the lightweight lock? The answer is Lock Record (lock record in the stack frame).
Let me explain here:
We know that the JVM memory structure can be divided into (1) heap (2) virtual machine stack (3) local method stack (4) program counter (5) method area (6) direct memory. The program counter and virtual machine stack are thread-private, and each thread has its own independent stack space. It seems that storing in the stack can well distinguish which thread has acquired the lock. In fact, JVM does do this.
First of all, JVM opens up a piece of memory in the current stack, which is called Lock Record (lock record), and copies the contents of Mark Word to Lock Record (that is, Lock Record stores the contents of the previous Mark Work, so why save the previous contents?
It's very simple, because we are about to modify the contents of Mark Word. Of course, we have to save it before we modify it, so that we can restore it later). After copying, we will start to modify Mark Word. How to modify it? Of course, replace Mark Word with the way of CAS! The Mark Word becomes the following:
Lightweight lock. Jpg
You can see that 30 bits are used in Mark Word to record the Lock Record we just created in the stack frame, and a lock flag of 00 indicates a lightweight lock, so it's easy to know which thread has acquired the lightweight lock.
Lightweight locks are based on the fact that when there are two or more threads competing for locks, in most cases, the thread holding the lock will quickly release the lock, that is, when there is a small amount of competition in the lock, the lock is usually held for a short time, and the thread waiting to acquire the lock can block itself without switching between user mode and kernel state. As long as the empty loop (this is called spin) for a while, it is expected that the thread holding the lock during the spin will release the lock immediately.
It is obvious that lightweight locks are suitable for situations where the lock competition is not fierce and the lock is held for a short time, on the contrary, if the lock competition is fierce or the thread does not release the lock for a long time after acquiring the lock, then the thread will spin (dead loop) and waste cpu resources.
Heavyweight mutex
When there are too many people who want to enter the treasure house, lightweight is not good, so we have to use the killer mace-heavyweight mutex lock. This is also the default implementation of Synchronized before Jdk1.6.
When the lock is lightweight, the thread needs to spin and wait for the thread holding the lock to release the lock, and then apply for the lock, but there are two problems:
1. There are a lot of spinning threads, that is, many threads are waiting for the thread holding the lock to release the lock, because the lock can only be acquired by one thread at the same time (in the case of Synchronized), which leads to a large number of thread failure to acquire the lock, can it not spin all the time?
two。 The thread holding the lock does not release the lock for a long time, so that the thread waiting outside to acquire the lock still cannot get the lock for a long time, can't it spin all the time?
In the above two cases, the thread waiting to acquire the lock is very uncomfortable, especially if both cases are satisfied (the thread that holds the lock for a long time does not release the lock for a long time). Therefore, JVM sets a limit on the number of spins. If the thread still does not acquire the lock after spinning a certain number of times, it can be regarded as a situation of fierce lock competition. At this time, the thread requests to cancel the lightweight lock and be promoted to a heavyweight mutex.
In lightweight locks, locks exist in the form of Lock Record, so when it comes to heavyweight locks, what form should they exist?
The complexity of the heavy lock is the highest, because the thread holding the lock needs to wake up the waiting thread when releasing the lock, and when the thread cannot get the lock, it needs to enter a blocking area to block and wait. At the same time, we know that there are still wait,notify conditions to wait and wake up, so the implementation of the heavy lock needs an additional mass killer-Monitor.
It is described in the book the Art of Java concurrent programming:
JVM implements method synchronization and code block synchronization based on entering and exiting Monitor objects, but the implementation details are different. Code block synchronization is implemented using monitorenter and monitorexit instructions, while method synchronization is implemented in a different way, details of which are not specified in the JVM specification. However, the synchronization of methods can also be achieved using these two instructions.
The monitorenter instruction is inserted at the beginning of the synchronous code block after compilation, while the monitorexit is inserted at the end of the method and at the exception, and the JVM must have a corresponding monitorexit to match each monitorenter. Any object has a monitor associated with it, and when and a monitor is held, it will be locked. When the thread executes the monitorenter instruction, it will attempt to acquire the ownership of the monitor corresponding to the object, that is, to acquire the lock of the object.
We take the HotSpot virtual machine as an example, which is implemented in C++, and C++ is also an object-oriented language, so this time, the virtual machine design team chooses to represent locks in the form of objects, while C++ also supports polymorphism. Monitor here is actually a kind of abstraction. The implementation of Monitor in the virtual machine uses ObjectMonitor, and the relationship between Monitor and ObjectMonitor can be compared to the relationship between Map and HashMap in Java.
Let's take a look at the real face of ObjectMonitor:
ObjectMonitor () {_ header = NULL; _ count = 0 NULL;// / used to record the number of times the thread acquired the lock _ waiters = 0, _ recursions = 0 the number of reentrants of the thread / lock _ object = NULL; _ owner = NULL;// points to the thread holding ObjectMonitor _ WaitSet = NULL / / the collection of threads in the Wait state _ WaitSetLock = 0; _ Responsible = NULL; _ succ = NULL; _ cxq = NULL; FreeNext = NULL; _ EntryList = NULL; / / so the collection of threads blocked waiting for the lock = 0; _ SpinClock = 0; OwnerIsThread = 0;}
It is strongly recommended that you take a look at the source code of ReentrantLock based on AQS (Abstract queue Synchronizer) implementation, because the idea of synchronizer implementation within ReentrantLock is basically the epitome of Monitor in Synchronized implementation.
First of all, there needs to be a pointer in the ObjectMonitor to the current thread that acquires the lock, that is, the owner above. When a thread acquires the lock, it will call the ObjectMonitor.enter () method to enter the synchronization code block. After acquiring the lock, the owner will be set to point to the current thread. When other threads try to acquire the lock, they will find the owner in ObjectMonitor to see if it is themselves, if so. Recursions and count increase by 1, which means that the thread has acquired the lock again (Synchronized is a reentrant lock, and the thread holding the lock can acquire the lock again), otherwise it should be blocked, so where do these blocked threads go?
Put it uniformly in EntryList. When the thread holding the lock calls the wait method (we know that the wait method causes the thread to abandon the cpu, release the lock it holds, and then block and suspend itself until another thread calls the notify or notifyAll method), the thread should release the lock, leave owner empty, and wake up the thread blocking the EntryList waiting to acquire the lock, then suspend itself and wait in the waitSet collection When another thread holding the lock calls the notify or notifyAll method, one or all of the threads in the WaitSet (notify) are moved from the WaitSet to the EntryList to wait for the competitive lock, and when the thread is about to release the lock, the ObjectMonitor.exit () method is called to exit the synchronized code block. Combined with the description in "the Art of Java concurrent programming", everything is clear.
Upgrading a lock to a heavyweight lock also requires two steps: (1) the revocation of a lightweight lock and (2) the upgrade of a heavyweight lock.
To undo the lightweight lock, of course, write the contents stored in the Lock Record in the stack frame back to the Mark Work, and then clean up the Lock Record in the stack frame. After that, you need to create an ObjectMonitor object and save the contents of the Mark Word to the ObjectMonitor (to make it easy to restore the Mark Word when the lock is unlocked, in this case, in the ObjectMonitor). So how do you find this ObjectMonitor object? Haha, that's right. Just record the pointer to the ObjectMonitor object in Mark Word. How do I modify the contents of the replacement Mark Word? Of course I will CAS!
The contents of a lock in Mark Word in the form of a heavyweight mutex are as follows:
Heavyweight lock. Jpg
You can see that 30 bits are used in Mark Word to hold the pointer to ObjectMonitor, and the lock mark bit is 10, indicating a heavyweight lock.
Heavyweight locks are based on the fact that when the lock is in serious competition, or when the lock is usually held for a long time, the thread waiting to acquire the lock should block and suspend itself and wait for the thread that acquired the lock to release the lock, so as to avoid wasting cpu resources.
The change of lock form
Now we can answer the question at the beginning of the article, "what does the lock in Java look like?" This problem, in different lock states, the lock shows different forms.
When the lock exists with a bias lock, the lock is the Thread ID in Mark Word, and the thread itself is the key to open the lock. The thread whose ID card is stored in the Mark Word will acquire the lock.
When the lock exists as a lightweight lock, the lock is the Lock Record of the lock record in the stack frame pointed to in the Mark Word. The key at this time is the domain, which is the virtual machine stack. Whoever has the Lock Record in the stack will get the lock.
When the lock exists as a heavyweight lock, the lock is the ObjectMonitor for Monitor in C++, and the key is the owner in ObjectMonitor. Whoever the owner points to gets the lock.
In the previous question, we said that the 32-bit virtual machine Mark Word is only four bytes. Can the lock be realized completely within these four bytes? This sentence is completely wrong before Jdk1.6 and is true in some cases after Jdk1.6. Now do you have a deeper understanding of this sentence?
In the real world, it is us who unlock the lock. Through the previous understanding, who is the one who unlocks the lock in the program world? Yes, it's a thread.
Now looking back at the questions at the beginning of the article, it's easy to give the answer, because it really started with the lock object used by Synchronized!
Thank you for your reading. the above is the content of "how to understand locks in programming". After the study of this article, I believe you have a deeper understanding of how to understand locks in programming. Specific use also needs to be verified by practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.