Multithreading and concurrency are not new content, but one of the innovations in Java language design is that it is the first mainstream language to directly integrate the cross-platform thread model and regular memory model into the language. The core class library contains
Thread
Class, which can be used to construct, start, and manipulate threads. the Java language includes a structure for cross-thread transmission of concurrency constraints --synchronized
And
volatile
. While simplifying the development of concurrent classes unrelated to the platform, it never makes the writing of concurrent classes more complicated, but makes it easier.
Synchronized quick review
Declaring a code block as synchronized has two important consequences:Atomicity)AndVisibility). Atomicity means that a thread can only execute code protected by a specified Monitoring object (LOCK) at a time, so as to prevent multiple threads from conflicting with each other when updating the sharing status. Visibility is more subtle; it has to deal with internal memory cache and compiler-optimized abnormal behavior. Generally, threads are in a way that does not need to be immediately visible to other threads (whether these threads are in registers, in a processor-specific cache, or by means of rescheduling or other compiler optimizations ), it is not restricted by the cached variable value, but if developers use synchronization, as shown in the following code, the Runtime Library will ensure that a thread updates the variable before the current
Yessynchronized
When a block is updated, it enters another one protected by the same Monitor (Lock ).synchronized
You can immediately see the updates to the variables. Similar rules exist in
volatile
Variable.
[Java]
View plaincopy
- Synchronized (lockobject ){
- // Update object state
- }
Therefore, to implement synchronization, you need to consider everything required to securely update multiple shared variables. There are no contention conditions and data cannot be damaged (assuming that the synchronized boundary location is correct ), make sure that other threads that are correctly synchronized can view the latest values of these variables. By defining a clear and cross-platform Memory Model (this model was modified in JDK 5.0 to correct some errors in the original definition), by following the simple rule below, it is possible to build a "write once, run anywhere" concurrent class:
At any time, as long as the variable you write may be read by another thread, or the variable you read is finally written by another thread, you must synchronize it.
But now, in the recent JVM, there is no contention for synchronization (when a thread has a lock, no other threads attempt to obtain the lock). The performance cost is still very low. (This is not always the case; synchronization in early JVMs has not been optimized, so many people think so, but now this has become a misunderstanding, people think that whether or not it is competing for use, synchronization has a high performance cost .)
Improvement on synchronized
It seems that synchronization is quite good, right? So why did the JSR 166 team spend so much time developing?java.util.concurrent.lock
What about the framework? The answer is simple-synchronization is good, but it is not perfect. It has some functional limitations-it cannot interrupt a thread waiting to get the lock, nor get the lock by voting. If you don't want to wait, you won't be able to get the lock. Synchronization also requires that the lock can be released only in the same stack frame as the stack frame in which the lock is obtained. In most cases, this is okay (and it interacts well with Exception Handling ), however, some non-block structure locks are more appropriate.
Reentrantlock class
java.util.concurrent.lock
InLock
The Framework is an abstraction of locking. It allows the implementation of locking as a Java class, rather than as a language feature. This is
Lock
Multiple implementations leave space. Different implementations may have different scheduling algorithms, performance characteristics, or locking semantics.ReentrantLock
Class
Lock
, It hassynchronized
With the same concurrency and memory semantics, some features such as lock voting, timed lock wait, and stoppedlock wait are added. In addition, it provides better performance in the case of fierce competition. (In other words, when many threads want to access shared resources, JVM can spend less time scheduling threads and spend more time on execution threads .)
ReentrantWhat does a lock mean? To put it simply, there is a lock-related acquisition counter. If a thread that owns the lock gets the lock again, it will add 1 to the acquisition counter, then the lock must be released twice before it can be truly released. This imitates
synchronized
If the thread enters the synchronized block protected by the monitor already owned by the thread, the thread is allowed to continue. When the thread exits the second (or later)
synchronized
Block, do not release the lock, only the thread exits it into the monitor to protect the firstsynchronized
Block to release the lock.
When viewing the sample code in Listing 1, you can seeLock
There is a major difference from synchronized-lock must be released in the Finally block. Otherwise, if the protected code throws an exception, the lock may never be released! This difference may seem insignificant, but in fact it is extremely important. If you forget to release the lock in the Finally block, a timer bomb may be left in the program. When one day the bomb explodes, it takes a lot of effort to find the source. With synchronization, JVM ensures that the lock is automatically released.
Listing 1. Use reentrantlock to protect code blocks.
[Java]
View plaincopy
- Lock = new reentrantlock ();
- Lock. Lock ();
- Try {
- // Update object state
- }
- Finally {
- Lock. Unlock ();
- }
In addition, compared with the current synchronized implementationReentrantLock
Achieve more scalability. (In future JVM versions, the competition performance of synchronized is likely to be improved .) This means that when multiple threads compete for the same lock
ReentrantLock
Generallysynchronized
Much less.
Compare the scalability of reentrantlock and synchronized
Tim Peierls uses a simple linear fully-equal pseudo-random number generator (PRNG) to construct a simple evaluation and use it to measuresynchronized
And
Lock
. This example is good, because each callnextRandom()
PRNG is doing some work, so this benchmark program is actually measuring a reasonable and true
synchronized
AndLock
Applications, rather than testing purely on paper or code that does nothing (just like many so-called benchmarking programs .)
In this benchmark program, there isPseudoRandom
Interface, which has only one methodnextRandom(int bound)
. This interface corresponds
java.util.Random
Class functions are very similar. When the next random number is generated, PRNG uses the latest number as the input, and maintains the last number as an instance variable, the focus is to prevent code segments updated in this state from being preemptible by other threads, so I will use some form of locking to ensure this. (
java.util.Random
Class .) ForPseudoRandom
Two implementations are built. One uses syncronized and the other uses
java.util.concurrent.ReentrantLock
. The driver generates a large number of threads, and each thread is frantically competing for a time slice, and then calculating the number of rounds that different versions can execute per second. Figures 1 and 2 summarize the results of different threads. This evaluation is not perfect, and only runs on two systems (one is dual Xeon running hyper-threading Linux, and the other is a single processor Windows System), but should be sufficient to show
synchronized
AndReentrantLock
Compared with the scalability.
The charts in Figure 1 and figure 2 show the throughput in units of calls per second. Different implementations are adjusted to 1 thread.synchronized
. Each implementation is relatively quickly concentrated on the throughput of a stable State. This State usually requires the processor to be fully utilized, and most of the processor's time is spent in actual work (computer random number) only a small amount of time is spent on Thread Scheduling expenses. You will notice that the synchronized version performs quite poorly when processing any type of contention, while
Lock
The version takes a relatively small amount of time on scheduling expenses, thus leaving space for higher throughput and achieving more effective CPU utilization.
Condition variable
Root classObject
Contains some special methods used in the threadwait()
,notify()
And
notifyAll()
Communication. These are advanced concurrency features that many developers have never used-this may be a good thing because they are quite subtle and easy to use improperly. Fortunately, as JDK 5.0 introduces
java.util.concurrent
There is almost no need for developers to use these methods.
There is an interaction between the notification and the lock-to be on the objectwait
Ornotify
You must hold the lock for this object. Just like
Lock
Is synchronized In summary,Lock
The framework includeswait
Andnotify
Is called
Condition)
.Lock
The object acts as the factory object bound to the condition variable bound to this lock, with the standardwait
And
notify
Different methods, for the specifiedLock
, There can be more than one conditional variable associated with it. This simplifies the development of many concurrent algorithms. For example,
Condition)
Javadoc of shows an example of bounded buffer implementation. This example uses two conditional variables: "Not full" and "not empty ", it is more readable (and more effective) than only one wait setting for each lock ).
Condition
Methods andwait
,notify
AndnotifyAll
The method is similar.
await
,signal
AndsignalAll
Because they cannot be overwritten.
Object
.
Unfair
If you view javadoc, you will see,ReentrantLock
A parameter of the constructor is a Boolean value, which allows you to choose
Fair (fair)Lock, orUnfair (unfair)Lock. Fair locks allow the thread to obtain locks in order of request locks, while unfair locks allow bargaining. In this case, the thread can sometimes obtain locks first than other threads that request locks first.
Why don't we make all the locks fair? After all, fairness is a good thing, but it is not good to be unfair, isn't it? (When children want to make a decision, they always yell "this is not fair ". We think fairness is very important, and the children know it .) In reality, fairness ensures that the lock is very robust and has a high performance cost. To ensure the bookkeeping and synchronization required for fairness, it means that the competing fair locks have lower throughput than the unfair locks. As the default setting, fairness should be set
false
Unless fairness is critical to your algorithm, it must be served strictly in the order of thread queuing.
What about synchronization? Is the built-in monitor lock fair? The answer surprised many people. They are unfair and never unfair. But no one complained about thread hunger, because JVM ensures that all threads will eventually get the lock they are waiting. Ensuring the fairness of statistics is sufficient in most cases, and the cost is much lower than the absolute fairness guarantee. Therefore, by default
ReentrantLock
It is "unfair". This fact is just a superficial process of synchronization. If you do not mind this during synchronization
ReentrantLock
Do not worry about it.
Figure 3 and figure 4 contain the same data as Figure 1 and figure 2. They only add a dataset for random number benchmark detection. This detection uses a fair lock instead of the default negotiated lock. As you can see, fairness has a price. If you need to be fair, you must pay the price, but do not use it as your default choice.
Good everywhere?
Looks likeReentrantLock
Bothsynchronized
Good -- all
synchronized
What you can do, it can do, it hassynchronized
With the same memory and concurrency semantics
synchronized
Features that are not available, but also have better performance under load. So should we forgetsynchronized
And no longer regard it as a good idea that has been optimized? Or even use
ReentrantLock
Rewrite our existingsynchronized
Code? In fact, several introductory Java programming books use this method in their multi-threaded chapter, fully used
Lock
For example, we only use synchronized as the history. But I think this is too good.
Do not discard synchronized
AlthoughReentrantLock
It is a very touching implementation. Synchronized has some important advantages, but I think it is definitely a serious mistake to rush to regard synchronized as just too busy.
java.util.concurrent.lock
The lock class in is a tool used for advanced users and advanced situations.. In general, unless you
Lock
There is a clear need for a certain advanced feature of, or there is clear evidence (rather than just doubt) that under certain circumstances, synchronization has become a bottleneck in scalability, otherwise synchronized should still be used.
Why is my opinion conservative in the use of an apparently "better" implementation? Becausejava.util.concurrent.lock
For the lock class in, synchronized still has some advantages. For example, when synchronized is used, you cannot forget to release the lock.
synchronized
The JVM will do this for you. You can easily forget to usefinally
Block release lock, which is very harmful to the program. Your program can pass the test, but there will be deadlocks in the actual work, it will be difficult to identify the cause (this is why it is not intended for junior developers at all)
Lock
.)
Another reason is that when the JVM uses synchronized to manage lock requests and releases, the JVM can include lock information when generating thread dump. These are very valuable for debugging because they can identify the sources of deadlocks or other abnormal behaviors.
Lock
The class is just a common class, And the JVM does not know which thread hasLock
Object. In addition, almost every developer is familiar with synchronized, which can work in all versions of JVM. Before JDK 5.0 becomes a standard (it may take two years from now on ),
Lock
Class will mean that not every JVM has the features to be used, and not every developer is familiar with them.
When should I replace synchronized with reentrantlock?
In this case, when should we useReentrantLock
What about it? The answer is very simple-when some features not available for synchronized are required, such as waiting for a time lock, waiting for an interrupted lock, no block structure lock, multiple conditional variables, or lock voting.
ReentrantLock
It also has the scalability benefit and should be used in highly competitive situations, but remember that most synchronized blocks have never been competing, so we can put high contention on one side. I suggest using synchronized for development until it does prove that synchronized is inappropriate, instead of simply assuming that
ReentrantLock
"Better performance ". Remember that these are advanced tools for advanced users. (Moreover, real advanced users prefer to select the simplest tool they can find until they think that simple tools are not applicable .). As always, we should first do a good job and then consider whether it is necessary to do it faster.
Lock
The framework is a substitute for synchronization compatibility.synchronized
Many features are not provided, and its implementation provides better performance in competition. However, these obvious advantages are not enough for use.
ReentrantLock
Replacesynchronized
. On the contraryYes
ReentrantLock
Ability to make a choice. In most cases, you should not select it -- synchronized works well and can work on all JVMs. More developers know about it and it is not easy to make mistakes. Only when needed
Lock
It is used. In these cases, you will be happy to have this tool.