Personal Knowledge points Summary--java concurrency

Source: Internet
Author: User
Tags cas

Java concurrency is a very deep problem, just a simple record of Java concurrency knowledge points. The water is too deep. Suppose not to spend a lot of time feeling completely hold, but the current energy is not enough, the interest is not here

What is thread safety

The behavior of a class is completely consistent with its specifications
When multiple threads are visiting a class. Regardless of the scheduling method the runtime environment uses or how these threads will run alternately. And in the keynote code, no matter what additional synchronization or collaboration, this class can show the correct behavior, it is called this class is thread-safe

Atomic operation (Atomic operation)

An atomic operation is an operation that is not interrupted by a thread-scheduling mechanism, such as when it starts. will run until the end, there is no context switch in between, it is non-cutting.

To give a common example: a++. This operation is not an atomic operation, so when multiple threads visit the call, A's end result is very likely not our expected value.

Because actually a++ this operation can be divided into three steps: Get the value of a, update the value of a, write back the value of a.

Cache consistency

In a multi-processor architecture with shared memory, each processor has its own cache and is regularly reconciled to main memory, providing different levels of cache consistency across different processor architectures.

This cache consistency can be deepened by the volatile keyword.

volatile keyword

Volatile is a weak synchronization mechanism that is used to ensure that update operations on variables are notified to other threads. When a variable is declared as a volatile type. The compiler and the runtime will notice that the variable is shared, so the actions on that variable are not reordered together. Volatile variables are not cached in registers or in places that are not visible to other processors. Therefore, the most recently written value is always returned when a variable of the volatile type is read.
But one thing needs to be noted. The operation of variables modified by volatile should also be atomic. Otherwise the same will be the first problem.
Like what:

int0;// 非原子性操作。使用volatile不能保证同步,改用Synchronizeda++;

And why is volatile able to achieve such a function?
This is to start with its implementation principle, under the x86 processor through the tool to get the JIT compiler generated assembly instructions to see what the volatile writes actually do.

0x01a3de1dmovb $0x0,0x1104800(%esi);0x01a3de24lock addl $0x0,(%esp);

There is a second line of code when a shared variable is modified with a volatile variable for a write operation. The lock directive is decorated.
And what does the lock command do?

    • Writes data from the current processor cache row back to system memory
    • The write-back operation causes invalid data to cache the memory address in the other CPU

As mentioned in the above cache consistency, each processor has its own cache in the multi-processor architecture of shared memory. and regularly coordinates with main memory, providing different levels of cache consistency in different processor architectures.
Then there are the following things:
In order to improve processing speed, not directly communicate with the memory, but first read the system memory data into the internal cache and then operate, but after the operation is not sure when it will be written back to memory. Assuming that a volatile variable is declared, the JVM sends a lock-prefixed instruction to the processor, writing the data from the cached row of the variable back to the system memory. And because of the cache consistency protocol, each processor will check its own cached value by sniffing the data that is propagated on the bus to be the flag. When the processor finds its own cache line the corresponding memory address is changed. The cache line of the current processor is set to an invalid state.

When the processor changes the data, it forces the data to be read from the system memory again into the processor cache.
This is also the principle of volatile implementation.

Re-order

The concept of reordering has just been summarized above. So what is reordering?

The simple understanding is that. When the program is running, assume that the JVM feels that the results between the two lines of code do not affect each other. Then it is possible to produce a disorderly result in the process of running.
Like what:

3;  4;

Under normal circumstances we will think A = 3 must be run first than b=4. Because it is above the B, it is not, because the result of B's operation is not dependent on the result of the previous row A. As a result, the JVM is able to reorder two lines of code, either a run first or B run first

Why should I use reordering?
Reordering is typically the compiler or runtime environment that is taken to optimize the performance of the program.

It can be divided into two categories: compiler reordering and run-time reordering.

Sequential consistency Model: the ideal model is. The order in which the various commands are run is unique and orderly, which is the order in which they are written in the code, regardless of the processor or other factors

Disadvantages of the sequential consistency model: low efficiency

The typical compiler reordering is by adjusting the order of instructions without changing the semantics of the program. As much as possible, reduce the number of registers read, stored, and fully take the memory value of the register.

Suppose the first instruction calculates a value assigned to variable A and is stored in the register. The second instruction is independent of a but requires a register (assuming that it will occupy the register where A is located), the third instruction uses the value of a and is independent of the second instruction. Then assume that the sequential conformance model is followed. A is placed in the register after the first instruction is run. A no longer exists when the second instruction is run. The third instruction run time A is read into the register again, and in this process, the value of a is not changed.

Normally the compiler swaps the position of the second and third directives so that a is present in the register at the end of the first instruction, and then the value of a is read directly from the register, reducing the overhead of repeated reads.

The problem of disorderly ordering in concurrency

The above summary of the reordering can cause chaos, the same, in parallel when the operation of local variables may also produce chaos. Since each thread has a separate stack, which is a separate thread space, when it runs, it reads the value of the variable from the main memory and puts it into its own thread stack, and writes the value back to the main memory space after the variable operation is complete.
However, there is a problem here, that is, the time the variable writeback operation occurs is not determined.

Even if thread a reads the data first than thread B, it is still possible that thread B will first write the value back to main memory, and finally the same result is not the value we want.

Happens-before (first occurrence)

The Java Memory Model (JMM) defines a partial-order relationship for all operations in a program. Called Happens-before. Suppose you want to ensure that the thread running operation B sees the result of operation a (whether a and B are running in the same thread). Then the happens-before relationship must be satisfied between A and B.

Suppose that two operating hours lack happens-before relationships. Then the JVM can reorder them arbitrarily.

When a variable is read by multiple threads and is written to at least one thread. Assuming that there is no sorting between read and write operations according to Happens-before, there is a problem of data contention.

Happens-before rules include:

    • Program Order rules : Assume that action A in a program precedes operation B, then a operation in the thread will run before the B operation
    • Monitor Lock rule : The unlock operation on the monitor lock must be run before the lock operation on the same monitor lock
    • volatile variable rule : A write operation on a volatile variable must be run before the read operation on the variable
    • Thread Initiation Rules : Calls to Thread.Start on the threads must run before any operations in the county
    • thread End Rule : Any operation in the county must be run before another thread detects that the thread has ended, or returns successfully in Thread.Join. or return false when calling Thread.isalive
    • Interrupt Rule : When a line thread is waiting has another thread called interrupt. Must be run before the interrupted thread is detected to the interrupt call
    • Finalizer Rule : The constructor of an object must be run before the finalizer is started
    • transitivity : Operation A is preceded by B. b before C, then a must be run before C

such as thread A:y=1-"lock M-" x=1-"unlock m
Thread B:lock M-"i=x-" unlock M-"j=y

When two threads use the same lock for synchronization, the happens-before relationship between them is: A's unlock m is running after the ability to run the lock m method of B, assuming that the two threads are synchronized at different locks, then cannot judge the sequence of actions between them, Because there is no happens-before relationship between the operations of these two threads

Lock/synchronized/reentrantlock (exclusive lock/pessimistic lock)

Synchronized
A built-in lock, when used to decorate a method or a block of code, guarantees that only one thread can run the code at the same time. There is also a thread that must wait until the current thread has run out of code before running the code block (before running. The thread is blocked). At the same time, it is also a reentrant lock (lock can be re-entered)

But here's a very critical place: when a thread visits a synchronized (this) of object to synchronize a block of code. Another thread still has access to the non-synchronized (this) synchronization code block in the object.

The double check lock mode was previously summarized in the singleton mode, but the double check lock mode has a very serious bug in some cases. is not written in this blog.

Here is an analysis of the double check lock mode

publicstaticgetInstance() {          ifnull) {                synchronized (Singleton.class) {                   ifnull) {                      new Singleton();                  }                }            }            return singleton;       

Because in the process of new Singleton (). In fact, it can be divided into very many steps, can be roughly divided into three things:

    • Allocating memory to an instance of singleton
    • Call the constructor of Singleton. Initialize fields
    • Point the Singleton object to the allocated memory space (singleton! = null)

However, the Java compiler agrees that the processor runs in a disorderly sequence. It's all possible for the second and third steps to run out of order, that is, assuming that the second step is disorderly (scheduled to the last step), when he is not running to switch to thread B, this time because the singleton has not been null and directly out if judgment, That way, when we run our code later, we're going to use an object that's not initialized with a constructor. "There seems to be an improvement at the JDK level so it works." The concrete is not clear, later changes again "

Lock

Lock is an interface that consists of several methods, including the following:
P.S. There are too many stares in the source code.

void lock();void lockInterruptibly();boolean tryLock();boolean tryLock(long time, TimeUnit unit);void unlock();Condition newCondition();

Lock it differs from the built-in locking mechanism, which provides an unconditional, rotation, timed, and interruptible Lock acquisition operation, all of which are explicitly locked and unlocked.

In a nutshell: Synchronized is the simplified version number of lock. Less function, but when the program is finished, it will voluntarily release the lock, and lock must manually release the lock

Reentrantlock
Reentrantlock it implements the lock interface. and provides the same mutual repulsion and memory visibility as the synchronized.
So why do you provide a new locking mechanism that is similar to a built-in lock?
Due to the limitations of the built-in lock under certain circumstances. For example, you cannot interrupt a process that is waiting to acquire a lock, or you cannot wait indefinitely while the request acquires a lock. The locking rules for non-clogging structures cannot be implemented.

And in the Reentrantlock. It can realize polling lock, timing lock, interrupt lock and many other locking methods, which also make its application scene a lot of other.

At the same time on performance: assuming that the more resources are spent on lock management and scheduling, the less resources the application gets. The better the lock is implemented, the fewer system calls and context switches are required, and the less memory synchronization traffic on the shared memory bus.
In the Java5. The performance of the Reentrantlock is much higher than the built-in lock, but the built-in lock in Java6 takes a similar algorithm used in Reentrantlock to manage the built-in locks, effectively improving scalability, so in Java6. Their throughput is very close.

In fairness, Reentrantlock is able to create an unfair lock (default) and can create a fair lock.


Fair Lock: Threads acquire locks in the order in which they are issued (first-come, first-served. No queue cuts)
Unfair lock: When a thread requests an unfair lock, it is assumed that the state of the lock becomes available at the same time as the request is made, then skips all waiting queues in the queue to get the lock immediately (that is, agreeing to queue jumping. The lock is available for direct access when requested)

And for fair locks and unfair locks, their efficiency is also obvious:
Fairness will greatly reduce efficiency due to the overhead of suspending threads and recovering threads.
Rather than fairness, because the lock is available at the time of the request, there is no additional action required. therefore more efficient.
In fact: to ensure that the blocked line Cheng Nen finally get the lock is enough, and the actual cost is much smaller.

When there is a fierce competition, there is a serious delay between recovering a suspended thread and the thread really starts running, which can affect efficiency. Let's say we use a non-fair lock (that is, the default way of Reentrantlock). When thread a releases the lock, B is awakened and then tries to acquire the lock, and C also requests this lock at the same time. Then C is very likely to get, use, and release this lock before B is fully awakened. It is also possible that the moment when B gets the lock is not postponed, and C has acquired the lock earlier.

When should we use a fair lock?
A fair lock should be used when the lock is held for a relatively long time, or if the average time interval for the request lock is longer.

How to choose between Synchronize and Reentrantlock
The above summed up so much, as if Reentrantlock's strengths than synchronize good too much, then why not directly cancel off synchronize? What do we do with our own choice?

Some of the most important strengths of synchronize are:

    • No need to manually release the lock, the program itself is completed.

      Assuming that you forget to release the lock in the finally in the process of using lock, you can actually have a big problem and possibly hurt other code, despite the fact that the code is formally running on the surface. So it is generally only to do some of the built-in lock can not complete the need to consider using reentrantlock, such as interrupt lock, polling lock and so on

    • Debugging problem: Synchronized in-thread storage gives you the ability to give which locks are acquired in which call frames. And be able to detect and identify the process of deadlock occurring.

      And reentrantlock It is just an object. The JVM does not know which threads hold this.

Non-clogging synchronization mechanism (optimistic lock)

The lock mechanism always has a pending wake-up operation, assuming that there are multiple threads requesting the lock at the same time, then the JVM needs to take advantage of the functionality of the operating system, and there is a significant overhead in the process of suspending and recovering the thread, and there is usually a longer interruption. It is assumed that the ratio between scheduling overhead and work overhead is very high when the competition is intense.

In addition, assuming that a thread is waiting for a lock, it cannot do anything else, at the same time assuming that the blocked thread has a higher priority, and the thread holding the lock has a lower priority, then the problem is more serious. That is, a priority reversal occurs. That is, a high-priority thread must wait for a low-priority thread to release the lock, causing its priority to be reduced to the level of the low-priority thread.

In recent times, a lot of concurrency algorithms are focused on the mechanism of non-clogging synchronization, such as: Lock-free algorithm

Lock-free algorithm (no lock)

The algorithm mainly uses a CAS mechanism (Compare and swap), which includes 3 values, the memory location to read and write V, the need for a comparative value A, the new value to write to B.
The principle of it is:

When and only if the value of V equals a, the CAS will atomically update the value of V with the new value B, otherwise it does not run whatever operation

When multiple thread knowledge uses CAS to update the same variable at the same time, only one of the threads can update the value of the variable. The other threads will fail.

However, the failed thread is not suspended. But was told to fail in this competition and to try again. Because a thread fails to block when it competes with CAS, it is able to decide whether to try again, or to run some recovery operations, or not to run whatever operation. This flexibility greatly reduces the risk of activity associated with locks.

Conclusion

The concurrency of the water is too deep, do not spend energy is difficult to hold, here simply record Java concurrency knowledge points

References
    • "Java Concurrency Programming"
    • Talk about the Concurrent series blog
    • The re-ordering of JVMs in Java-that is, why you use reordering in Java

Personal Knowledge points Summary--java concurrency

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.