Understanding of Java thread Synchronization
We can run various computer software programs on the computer.
Each running program may include multiple threads that run independently. Threads (thread) is a stand-alone program that has its own dedicated run stack.
Threads may share some resources with other threads, such as memory, files, databases, and so on. Conflicts can occur when multiple threads are reading and writing the same shared resource at the same time.
At this time, we need to introduce the thread "sync" mechanism, that is, the threads should have a arrival, not a swarm of rush into the crowd. The word sync is translated from English synchronize (making simultaneous occurrences). I don't understand why we should use this very misleading word.
Since we all use it, we'll have to do it. The true meaning and literal meaning of thread synchronization are the opposite.
The true meaning of thread synchronization is actually "queuing": Queues between threads, one for the shared resource, not for the same operation. So the 1th thing to keep in mind about thread synchronization is that thread synchronization is the thread queue. Synchronization is queuing. Thread synchronization is designed to prevent the thread from "synchronizing" execution.
This is really a boring tongue twister. The 2nd word about thread synchronization that needs to be firmly remembered is "sharing". Only read and write access to shared resources requires synchronization.
If it is not a shared resource, there is no need for synchronization at all. The 3rd thing to keep in mind about thread synchronization is that only "variables" require synchronous access. If the shared resource is fixed, it is equivalent to "constant", and the thread does not need to synchronize to read constants at the same time.
At least one thread modifies a shared resource, in which case synchronization between threads is required.
As for thread synchronization, the 4th that needs to be kept firmly in mind is that the code that multiple threads access to a shared resource may be the same code, or it may be different code, whether or not the same code is executed, as long as the Threads ' code accesses the same mutable shared resource, the threads need to synchronize.
To deepen your understanding, let me give you a few examples.
There are two buyers, their work is the same, are followed by the following steps: (1) to the market, to find and purchase potential samples.
(2) Return to the company and write a report. These two people have the same job, they need to buy samples, they may buy the same kind of samples, but they will never buy the same sample, they do not have any shared resources.
So, they can do their own work, do not interfere with each other.
The two buyers are equivalent to two threads, and two buyers follow the same work steps, equivalent to two threads executing the same piece of code. Next, add a work step for the two buyers.
The buyer needs to arrange its own work plan according to the information published in the company's "bulletin Board". The two buyers are likely to walk to the front of the bulletin board at the same time and watch theInformation. This is not a problem at all.
Since the bulletin board is read-only, neither buyer will change the information on the bulletin board. Add a role below.
An office executive this time, also went to the bulletin board, ready to change the information on the bulletin board. If the administrator arrives at the bulletin board first, and is revising the bulletin board's contents. Two buyers this time, happened to be there.
The two buyers will have to wait for the administrative staff to complete the changes before they can view the modified information. If the executives arrive, two buyers are already watching the bulletin board.
Then the executives will have to wait for two buyers to record the current information before they can write new information. In both cases, access to the bulletin board by the administrative staff and the buyer needs to be synchronized. Because one of the threads (administrative staff) has modified the shared resource (bulletin board).
And we can see that the executive's workflow and the buyer's workflow (executing code) are completely different, but they need to sync because they have access to the same variable shared resource (bulletin board).
The sync lock tells us why we want the thread to sync, so let's see how we can synchronize threads. Thread synchronization of the basic implementation of the idea is relatively easy to understand. We can add a lock to the shared resource, and this lock has only one key.
Which thread acquires the key, has the right to access the shared resource. Life, we may also encounter such an example. Some of the supermarket's outside provide some automatic storage boxes. Each storage box has a lock, a key. People can use lockers with keys, put things in lockers, lock lockers, and take the keys away. In this way, the bin is locked and no one else can access the locker. (Of course, the real locker key can be taken away and copied, so don't put valuables in the supermarket's storage box.) So many supermarkets have adopted electronic password lock. Thread sync lock this model looks intuitive.
However, there is still a serious problem unresolved, where the sync lock should be added. Of course it's added to the shared resources.
Fast-responding readers will be the first to answer. Yes, if possible, we will of course try to add the sync lock to the shared resource. Some of the more perfect shared resources, such as file systems, database systems, and so on, themselves provide a relatively perfect synchronization lock mechanism.
We do not need to lock these resources separately, and these resources have their own locks. However, in most cases, the shared resources that we access in our code are relatively simple shared objects.
There is no place in these objects for us to lock. Readers may suggest: Why not add a new area within each object, specifically to lock it up. This design is certainly feasible in theory. The problem is that thread synchronization is not very common. If because of this small probability event, all objects inside all open a lock space, will bring great space waste. Not pay.Lost. So, the modern programming language design idea all is to add the synchronous lock to the code section. Specifically, the sync lock is added to "access the code snippet for a shared resource."
It's important to remember that the sync lock is added to the code snippet. The synchronization lock is added to the code snippet, which solves the problem of space waste.
But it increases the complexity of the model and increases the difficulty of our understanding.
Now let's analyze the thread synchronization model for "Sync Lock Plus on code snippet". First, we have solved the problem of where to add the sync lock.
We have determined that the sync lock is not added to the shared resource, but is added to the code snippet that accesses the shared resource. Second, the question we have to solve is what kind of lock should we add to the code snippet. This issue is the focus of the focus.
This is a particular issue we should pay attention to: access to the same shared resources of different code snippets, should be added to the same synchronization lock, if the addition of a different synchronization lock, then simply can not play the role of synchronization, there is no meaning.
This means that the synchronization lock itself must also be a shared object between multiple threads.
Synchronized keywords in the Java language to deepen your understanding, give examples of how several code snippets are synchronized. Synchronous lock models for different languages are the same. It's just a different way of expressing things. Here we take the current most popular Java language for example. In the Java language, use the Synchronized keyword to lock the code segment.
The entire grammatical form is synchronized (synchronous lock) {//access to shared resources, need to sync the code snippet} here in particular, the synchronization lock itself must be a shared object. ... F1 () {Object lock1 = new Object ();//Generate a Sync lock synchronized (LOCK1) {//code snippet A//access shared resource Resource1//Require synchronization}} above this Paragraph code doesn't make any sense. Because the sync lock is generated inside the function body. Each thread invokes this code and a new synchronization lock is generated. So there are different sync locks used between multiple threads.
The goal of synchronization is not achieved at all.
Synchronization code must be written in the form of the following to make sense.
public static final Object Lock1 = new Object (); ... F1 () {synchronized (LOCK1) {//LOCK1 is a public sync lock//code snippet A//access shared resource Resource1//need to sync} You don't have to declare the sync lock static and public, but you Be sure to use the same one between the related synchronization codesSync lock. When you talk about this, you're going to wonder what this sync lock is.
Why do you declare an object object casually, you can be a synchronous lock. In Java, the concept of synchronous locks is the case. Any object reference can be used as a synchronous lock. We can interpret object reference as the memory address of an object in a memory allocation system. Therefore, to ensure that synchronized code snippets are using the same synchronization lock, we need to ensure that the Synchronized keyword for these sync code snippets uses the same object Reference, the same memory address.
This is why I used the final keyword when I declared lock1 in the previous code, which is to ensure that the Lock1 object reference remain unchanged throughout the system. Some inquisitive readers may want to continue to learn more about the actual operating mechanism of the synchronzied (synchronous lock). In the Java Virtual Machine specification (you can search with keywords like "JVM Spec" in Google), there is a detailed explanation of the synchronized keyword. Synchronized will be compiled into monitor enter, ... monitor exit and so on. Monitor is the actual sync lock.
Each object reference is conceptually corresponding to a monitor. These implementation details are not the key to understanding the synchronization lock model.
We continue to look at a few examples to deepen our understanding of the synchronous lock model.
public static final Object Lock1 = new Object (); ... F1 () {synchronized (LOCK1) {//LOCK1 is a public sync lock//code snippet A//access shared resource Resource1//Need sync}} ... f2 () {synchronized (lock1 {//LOCK1 is a public sync lock//code snippet B//Access shared resource Resource1//Need to sync}} in the above code, code snippet A and code segment B are synchronized.
Because they are using the same sync lock lock1.
If there are 10 threads executing code snippet A at the same time and 20 threads executing code snippet b simultaneously, then all 30 threads are synchronized. These 30 threads are competing for a synchronous lock lock1. At the same time, only one thread can get lock1 ownership, and only one thread could execute code snippet A or code snippet B. Other competing failed threads can only be temporarilyStop running to enter the Ready (Ready) queue for the sync lock. Several thread queues are hung below each synchronization lock, including Ready (Ready) queues, waiting queues, and so on. For example, a lock1-ready queue can be called a lock1-ready queue.
There may be multiple threads running in each queue. Note that the thread that failed the competing sync lock entered the Ready (Ready) queue for the synchronization lock, not the call queue (waiting queue) to be described later, or the wait queue. The threads in the ready queue are always ready to compete for the sync lock, ready to run at all times.
The threads in the call queue can only wait until a certain signal is notified before they can be transferred to the ready queue and ready to run. A thread that successfully obtains a sync lock, and then releases the synchronization lock after the synchronization code snippet has been executed. The other threads in the ready queue of the synchronization lock continue to compete for the next round of sync locks.
The winner can continue to run, and the loser stays in the ready queue. Therefore, thread synchronization is a very resource-intensive operation. We try to control the range of code snippets that thread synchronizes. The smaller the range of code snippets that is synchronized, the better.
We use a noun "sync granularity" to indicate the scope of the synchronization code snippet.
Synchronization granularity In the Java language, we can directly add the Synchronized keyword directly to the definition of the function.
Like what. Synchronized ... F1 () {//F1 code snippet} This code is equivalent to ... F1 () {synchronized (this) {//Synchronous lock is the object itself//F1 code segment}}} The same principle applies to static (s
tatic) functions such as. ... static synchronized ... F1 () {//F1 code snippet} This code is equivalent to ... static ... F1 () {synchronized (Class.forName (...))
{//Sync lock is the class definition itself//F1 code segment}} However, we should try to avoid this lazy approach that directly adds synchronized to the definition of a function. Because we want to control the synchronization granularity. The smaller the code snippet to synchronize, the better.
The smaller the range of synchronized control, the better.
We should not only reduce the length of the synchronization code to work hard, we also pay attention to the subdivision of the synchronization lock.
For example, the following code is public static final Object Lock1 = new Object (); ... F1 () {synchronized (LOCK1) {Lock1 is a public sync lock//code snippet A//access shared resource Resource1//Need sync} ... f2 () {synchronized (LOCK1) {//LOCK1 is a public sync lock//code snippet B/
/Access shared resources Resource1//Need sync}} ... f3 () {synchronized (LOCK1) {//LOCK1 is a public sync lock//code snippet C//Access shared resource RESOURCE2//need to sync } ... f4 () {synchronized (LOCK1) {//LOCK1 is a public sync lock//code snippet D//Access shared resource RESOURCE2//Need to sync}} The above 4-segment synchronization code, using the same sync lock Loc K1.
All threads that invoke any piece of code in the 4-paragraph code need to compete for the same synchronization lock Lock1.
We analyzed it carefully and found that it was not necessary. Because F1 () code snippet A and F2 () of code snippet B access the shared resources that are RESOURCE1,F3 () code Snippets C and F4 (), the shared resources accessed are resource2, and they are not necessarily competing for the same sync lock lock1. We can add a sync lock lock2.
The Code for F3 () and F4 () can be modified to: public static final Object Lock2 = new Object (); ... f3 () {synchronized (LOCK2) {//Lock2 is a public sync lock//code snippet C//Access shared resource RESOURCE2//Need sync}} ... f4 () {synchronized (Lock2 {//Lock2 is a public sync lock//code snippet D//Access shared resource RESOURCE2//Need to sync}} In this way, F1 () and F2 () compete Lock1, while F3 () and F4 () compete Lock2.
In this way, separately competing for two locks can greatly reduce the probability of synchronous lock competition, thus reducing the overhead of the system. The signal synchronization lock model is simply the simplest synchronization model.
At the same time, only one thread can run the synchronization code. Sometimes we want to deal with more complex synchronization models, such as producer/consumer models, read-write synchronization models, and more. In this case, the synchronization lock model is not enough. We need a new model. This is what IThe semaphore model that we are going to tell.
The semaphore model works as follows: When a thread is running, it can take the initiative to stop and wait for a semaphore to be notified, at which point the thread enters the call (waiting) queue of the semaphore, and waits until the notification continues.
In many languages, synchronous locks are represented by specialized objects, and object names are usually called Monitor.
Similarly, in many languages, semaphores are usually represented by a specific object name, such as Mutex,semphore. The signal model is much more complex than the synchronous lock model. In some systems, the semaphore can even be synchronized across processes.
In addition, some semaphores even have counting function, which can control the number of threads running concurrently. We don't need to consider such a complicated model. All those complex models are derived from the most basic models.
As long as you master the most basic semaphore model-"wait/notify" model, the complex model will be solved. Let's take the Java language as an example.
The concept of synchronous lock and semaphore in the Java language is very vague, there is no special object noun to represent synchronous lock and semaphore, only two key words related to--volatile and synchronized. Although this ambiguity leads to unclear concepts, it also avoids the misunderstandings caused by such nouns as monitor, Mutex and Semphore.
We don't have to be obsessed with nouns, we can focus on understanding the actual operating principle. In the Java language, any object reference can be used as a synchronous lock.
In the same way, any object reference can be used as a semaphore.
The wait () method of an Object object is waiting for the notification, and the Notify () method for the object is to notify.
The specific invocation method is (1) a notification that waits for a semaphore public static final Object signal = new Object (); ... F1 () {synchronized (Singal) {//First we have to get this semaphore. This semaphore is also a synchronous lock//Only after successfully acquiring the signal signal and synchronizing the lock, we may enter this code signal.wait (); This is to give up the semaphore. This thread wants to enter the signal semaphore's call (Waiting) queue//poor.
Hard to get the signal of the hand, so was abandoned//wait until the notification, from the call (waiting) queue to the Ready (Ready) queue//Go to the Ready queue, a step closer to the CPU core, there is a chance to continue to execute the following code. Still need to put the sigNAL sync Lock to get the competition, you can really continue to execute the following code.
Commiseration AH.
... } Note the meaning of the signal.wait () in the above code. Signal.wait () can easily lead to misunderstanding.
Signal.wait () does not mean that signal begins to wait, but rather that the current thread that runs this code starts waiting for the signal object, which goes into the signal object's calling (waiting) queue. (2) Notification of a signal quantity ... f2 () {synchronized (Singal) {///First, we also need to get the semaphore.
It is also a synchronous lock. Only after the successful acquisition of signal this semaphore and synchronization lock, we can enter this code signal.notify ();
Here, we notify the signal of a thread in the called queue. If a thread waits for this notification, that thread will go to the ready queue//But this thread continues to have the signal this sync lock, this thread still continues to execute/hehe, although this thread kindly notifies other threads,//However, this thread may not be so sharp sense, abandon the hand of the sync lock/
This thread continues to execute the following code ...} What you need to be aware of is the meaning of signal.notify (). Signal.notify () is not the notification signal the object itself.
Instead, it notifies other threads that are waiting for the signal semaphore.
The above is the basic usage of object's Wait () and notify ().
In fact, wait () can also define the waiting time, and when the thread is in the calling queue of a semaphore, waiting for a long enough time, it waits for nothing, no more waiting, and moves itself from the queue to the ready queue.
In addition, there is a notifyall () method that indicates all threads in the call queue.
These details do not have an impact on the general situation.
Green thread green thread is a concept relative to the operating system thread (Native thread). The operating system thread (Native thread) means that the threads in the program actually map to the operating system's threads, and that the thread's running and scheduling are green threads controlled by the operating system, meaning that the threads inside the program do not actually map to the operating system's threads.
Instead, it is scheduled by the language running platform itself. The current version of the Python-language thread can be mapped to the operating system thread. The current version of the Ruby language thread belongs to the green thread and cannot be mapped to the operating system's line, so the Ruby language thread runs slowly. Does it mean that green threads are slower than operating system threads? Of course not. In fact, the situation may be just the opposite. Ruby is a special example.
The thread scheduler is not very mature. Currently, the thread's popular implementation model is green thread. Stackless Python, for example, introduces a more lightweight concept of green threading.
On-line concurrent programming, both running speed and concurrent load are superior to Python.
Another more notable example is Erlang (an Open-source language developed by Ericsson). Erlang's green thread concept is very thorough. Erlang threads are not called thread, but are called process. This is easy to confuse with the process.
Here to be careful to distinguish. There is no need for synchronization between ErLang process. Because all the variables in the Erlang language are final, the value of the variable is not allowed to change.
So there's no need to sync at all. Another benefit of final variables is that there is no cross reference between objects, and it is not possible to form a ring of association, and the associations between objects are unidirectional and tree-like. Therefore, the memory garbage collection algorithm efficiency is also very high. This allows Erlang to achieve the soft real time (soft real-time) effect. This is not an easy task for a language that supports memory garbage collection.