Volatile

Volatile does two things:

1 Ensure memory visibility

Memory visibility means that when one thread changes the value of a variable, another thread can immediately see it.

2. Prohibit order rearrangement (orderliness)

Since the CPU may do some reordering of the instructions at runtime based on the context information, the order of execution may not be the same as we expected. After volatile, the CPU will unoptimize the reordering of the variables and ensure that the order of execution is the same as our code expects.

Volatile is most commonly used in DCL singletons, where volatile variable reads are almost identical to ordinary variables, but write operations are less efficient (because of memory barriers), but still more efficient than locking. Volatile does not guarantee atomicity, and if volatile meets the requirements, volatile is the best option; if not, locking should be used.

Volatile guarantees visibility and order, but not atomicity, and can be used with static and member variables.

synchronized

Volatile guarantees “visibility” and “order,” but not “atomicity,” whereas synchronized guarantees both “visibility,” “order,” and atomicity.

Synchronized can be used to refer to “member methods,” “static methods,” and “code blocks.” Synchronized can be used to refer to a member method when the lock object is the current object, this. When decorating static methods, the lock object is the current class object; When decorating a code block, the lock object is the specified object.

T.class public static synchronized void Test (){} This public synchronized void test(){} public synchronized void test(){}Copy the code

Synchronized is reentrant, that is, if a thread has a lock that a synchronized method needs, it can call another method that needs the same lock and enter directly. But be sure to distinguish between this (member method) and.class (static method) locks.

Public synchronized void test1(){synchronized void test2(); synchronized void test1(){synchronized void test2(); Test3 (); test3(); test3(); test3(); Public static synchronized void test3(){} public static synchronized void test3(){}Copy the code

Synchronized = synchronized = synchronized = synchronized = synchronized

Public void test1(){// synchronized lock1 (lock1) {doSomething(); // Request lock2 synchronized(lock2) {}} public void test2(){// hold lock2 synchronized(lock2) {doSomething(); Synchronized (lock1) {}}}Copy the code

Test1 () and test2() run on T1 and T2, respectively. T1 holds Lock1 and T2 holds lock2. T1 is equal to T2’s lock2, but T2 needs Lock1 to release lock2. Avoid this problem in development and try not to have two locks at the same time.

Synchronized does not respond to interrupts:

Thread Thread = new Thread {run(){synchronized(lock) {}}}; // Interrupt the thread, assuming the thread is in the BLOCKED state and has not acquired the lock. Thread.interrupt ();Copy the code

Above, we called thread.interrupt() and the thread was still BLOCKED, not interrupted, which is a disadvantage of synchronized.

The classification of the lock

Pessimistic and optimistic locks: synchronized is a synchronized system that assumes a race will occur each time it requests the lock, and thus blocks and is inefficient. Optimistic locks are based on CAS. Threads will loop to check whether the conditions are met. They will not block and are highly efficient, such as reentrant locks, read/write locks, etc.

Reentrant locks and non-reentrant locks: A reentrant lock means that if you already have a required lock, you do not need to apply for a lock to re-enter code that requires the same lock. A non-reentrant lock requires a new lock every time a code that requires the same lock is entered. Most locks in Java are reentrant locks.

Shared lock and exclusive lock: A shared lock is a lock that can be shared by multiple threads. An exclusive lock is a lock that can be occupied by only one thread. The most common shared locks are read locks.

Spin lock and a spin lock: spin lock if one thread is not lock, so will try cycle, so that we can avoid falling into a blocking state, but if a long time can’t get, then white spin and waste of the CPU, so jdk1.6 upgrade spinlocks for adaptive spin, each spin longer time, to avoid wasting CPU; A non-spinlock cannot be acquired and is directly blocked.

Fair lock and non-fair lock: Fair lock means that whoever applies for the lock first is allowed to acquire the lock first and is strictly executed in a queue. If A is in A wait() state, we can let B execute first instead of waiting, so it is more efficient. The reentrant ReentrantLock constructor can then specify fair or not.

Interruptible and non-interruptible locks: Interruptible locks are those that respond to interrupts, such as ReentrantLock; Non-reentrant locks are those that do not respond to interrupts, such as synchronized.

Biased locking, lightweight locking and heavyweight locking

  • Biased lock: the first thread to acquire the lock is biased. If the lock is not acquired by another thread, the thread holding the biased lock will no longer need to synchronize and will evolve into a lightweight lock in case of contention.
  • Lightweight lock: The CAS spin is used to acquire the lock, which does not block, has low overhead, and evolves into a heavyweight lock when there is competition.
  • Heavyweight locks: they are generally synchronized, blocking and waiting.

JVM optimizations for synchronized

  • 1. Evolution: no lock -> Biased lock -> lightweight lock -> heavyweight lock. Synchronized evolves from lightweight to heavyweight according to the level of competition.
  • Lock removal: If there are unnecessary locks, they will be removed. For example, if there is no contention on local variables, they will be removed at compile time to improve efficiency.
  • Lock coarsening: If there are fragments of code that do a lot of locking in a short period of time, they will be merged directly to avoid unnecessary locking operations.
public void test(){ Object lock = new Object(); Synchronized (lock) {}} public synchronized void test(){// synchronized(); Synchronized (this) {}} synchronized(this) {}}Copy the code

The commonly used locks

Synchronized is an implicit lock, does not respond to interrupts, and causes deadlocks, which is a disadvantage that an explicit lock solves.

1 understand the CAS

The principle of synchronized is that it will block when it encounters a lock, which will cause a thread context switch, which is very inefficient. However, CAS is an atomic operation directly supported by the operating system, and the thread will loop to detect whether the condition is met through spin, thus avoiding a thread context switch.

public final boolean compareAndSet(int expect, int update); 
Copy the code

The implementation of CAS is simple: if the current value is Expect, it updates to update, and returns true if the update is successful, and false otherwise. The entire step is an atomic operation. Let’s look at the implementation of AtomicInteget:

public final int incrementAndGet(){
    for(;;){
        int current = get();
        int next = current + 1;
        if(compareAndSet(current,next)) return next;
    }
}
Copy the code

If the update fails, it indicates that the value has been changed by another thread. Try again until the update succeeds. Next is returned.

2 the explicit lock

ReentrantLock

lock()/unlock(); The normal locking method, lock() blocks until the lock is successfully acquired. It can respond to interrupts, throwing InterruptedException tryLock() if it is (synchronized next door is confused); Try to acquire a lock, return it immediately, without blocking, return true if successful, false otherwise. This API is useful for avoiding deadlocks. NewCondition (); Create a condition. Multiple conditions can be associated with a Lock.

It is worth mentioning that the constructor of ReentrantLock can accept a Boolean value indicating whether it is fair, and passing true will give a fair lock.

public class Test { private final Lock lock = new ReentrantLock(); private volatile int number; Public void add(){// add lock.lock(); // try {count++; }finally {// Put the suggestion in finally to ensure that lock.unlock() can be executed; } } public int getNumber(){ return number; }}Copy the code

The simple usage is as above, and the unlocking operation suggestion is put in the finally to ensure that it can be unlocked

The optimization of the system to synchronized greatly improves the efficiency of synchronized. Therefore, generally speaking, if synchronized can meet the conditions, synchronized can be used directly; if not, ReentrantLock can be considered.

Read-write lock ReadWriteLock

readLock(); Acquire a read lock. A read lock is a shared lock that can be shared by multiple threads. That is, multiple threads can read simultaneously. writeLock(); Acquire a write lock. A write lock is an exclusive lock, that is, only one thread can write at a time.

According to the characteristics of the read/write lock, it can be seen that the read/write lock is suitable for the situation of more read and less write.

Others are segmented locks, which are used in ConcurrentHashMap and will not be discussed here.

3 Explicit conditions

An explicit lock is used to replace synchronized, so an explicit condition is used to replace wait()/notify().

The API for the explicit Condition is as follows:

public interface Condition { void await() throws InterruptedException; // Equivalent to Object await() void awaitUninterruptibly(); // uninterruptible await() //.... Other await() variants void signal(); // Equivalent to object.notify () void signalAll(); // Equivalent to object.notifyAll ()}Copy the code

Upon entering the await queue, the lock is released, the CPU is released, and when another thread wakes it up or the wait times out or interrupts, the lock needs to be reacquired and the await() method exits.

public class Test { private final Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private volatile boolean stop = false; Private void test(){try {// lock lock.lock(); Try {// check for termination condition while(! Stop) {// wait for condition.await(); }}finally {// Release the lock lock.unlock(); }}catch(InterruptedException e) {// Interrupt (); }} private void stop(){// add lock. Lock (); Try {// update condition stop = true; / / wake condition. The signal (); }finally {// unlock lock.unlock(); }}}Copy the code

As in the above code, condition is to be used with lock, await() and signal() are to be between lock() and unlock(), as are wait()/notify() and synchronized.

Lock design ideas broad spirit, interested can understand the LockSupport and AQS, we will talk in the next section.