Disclaiming: This article is my own self-study of moOCs teacher Wukong “Play with Java Concurrent tools, master JUC, Become concurrent Versatile” after the lock part organized into course notes.

If there is infringement, please send me a private message and delete this article for the first time.

The Lock Lock

1. The Lock interface

1.1 Introduction, status and role

  • A lock is a tool for controlling access to a shared resource.

  • Lock and synchronized, the two most common locks, can achieve the purpose of line security, but there are great differences in use and function.

  • Lock is not intended to replace synchronized, but rather to provide advanced functionality when synchronized is inappropriate or inadequate.

  • The most common implementation class for the Lock interface is ReentrantLock

  • Typically, a Lock allows only one thread to access the shared resource. Sometimes, however, special implementations allow concurrent access, such as ReadLock in ReadWriteLock.

1.2 Why synchronized is not enough? Why Lock?

1.3 Methods (Lock, trylock, lockInterruptibly)

Four methods are declared in Lock to acquire the Lock

Lock(), tryLock(), tryLock(long time, TimeUnit Unit), and lockInterruptibly()

So what’s the difference between these four methods?

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** * Description: Lock does not automatically release the Lock when an exception occurs, as synchronized does, so it is best practice to release the Lock in finally to ensure that the Lock is released when an exception occurs */
public class MustUnlock {

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try{
            // Obtain the resource protected by this lock
            System.out.println(Thread.currentThread().getName()+"Start the mission.");
        }finally{ lock.unlock(); }}}Copy the code

import java.util.Random;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** * Description: Use tryLock to avoid deadlocks */
public class TryLockDeadlock implements Runnable {


    int flag = 1;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        TryLockDeadlock r1 = new TryLockDeadlock();
        TryLockDeadlock r2 = new TryLockDeadlock();
        r1.flag = 1;
        r1.flag = 0;
        new Thread(r1).start();
        new Thread(r2).start();

    }

    @Override
    public void run(a) {
        for (int i = 0; i < 100; i++) {
            if (flag == 1) {
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("Thread 1 has acquired lock 1.");
                            Thread.sleep(new Random().nextInt(1000));
                            if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                                try {
                                    System.out.println("Thread 1 has acquired lock 2");
                                    System.out.println("Thread 1 successfully acquired both locks.");
                                    break;
                                } finally{ lock2.unlock(); }}else {
                                System.out.println("Thread 1 failed to acquire lock 2, retry"); }}finally {
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000)); }}else {
                        System.out.println("Thread 1 failed to acquire lock 1, retry"); }}catch(InterruptedException e) { e.printStackTrace(); }}if (flag == 0) {
                try {
                    if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("Thread 2 got lock 2.");
                            Thread.sleep(new Random().nextInt(1000));
                            if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                                try {
                                    System.out.println("Thread 2 got lock 1.");
                                    System.out.println("Thread 2 successfully acquired two locks.");
                                    break;
                                } finally{ lock1.unlock(); }}else {
                                System.out.println("Thread 2 failed to acquire lock 1, retry"); }}finally {
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000)); }}else {
                        System.out.println("Thread 2 failed to acquire lock 2, retry"); }}catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code

package lock.lock;

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** * TODO */
public class LockInterruptibly implements Runnable {

    private Lock lock = new ReentrantLock();
public static void main(String[] args) {
    LockInterruptibly lockInterruptibly = new LockInterruptibly();
    Thread thread0 = new Thread(lockInterruptibly);
    Thread thread1 = new Thread(lockInterruptibly);
    thread0.start();
    thread1.start();

    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    thread1.interrupt();
}
    @Override
    public void run(a) {
        System.out.println(Thread.currentThread().getName() + "Attempt to acquire lock");
        try {
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + "Lock obtained");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "Interrupted during sleep.");
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "Release the lock."); }}catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "Lock acquisition was interrupted."); }}}Copy the code

4. Visibility assurance

2. Lock classification

◆ These categories are not mutually exclusive, i.e. multiple types can coexist: it is possible for a lock to belong to both types. ◆ For example, ReentrantLock is both a mutex and a ReentrantLock

3. Optimistic (non-mutex synchronization) and Pessimistic (mutex synchronization) locks

3.1. Why is a non-mutex lock born

Disadvantages of mutex lock

  • Performance disadvantages of blocking and wake up

  • Permanent block: If the thread holding the lock is permanently blocked, such as an infinite loop, deadlock, or other active problem, the threads waiting for the lock to be released will never execute

  • Priority inversion

3.2. What are optimistic locks and pessimistic locks

Optimistic locking

  • It does not lock the object because it thinks it is processing the operation without interference from other threads

  • When updating, I will compare whether the data has been changed by others during my modification: if it has not been changed, it means that only I am in operation, so I will modify the data normally

  • If the data is different from what I got at the beginning, it means that someone else has changed the data during this period of time, then I cannot continue the data update process just now, AND I will choose to give up, report errors, retry and other strategies

  • Optimistic locking is generally implemented by CAS algorithm

3.3. Typical examples

◆ Pessimistic lock: synchronized and lock interface

◆ Typical examples of optimistic locking are atomic classes, concurrent containers, etc

◆ Code demo

import java.util.concurrent.atomic.AtomicInteger;

/** * TODO */
public class PessimismOptimismLock {

    int a;

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();
        atomicInteger.incrementAndGet();
    }

    public synchronized void testMethod(a) { a++; }}Copy the code

If the version of the remote repository is not the same as the version of the local repository, it means that someone else has changed the remote code, and our commit will fail. If the remote and local version numbers are the same, we can smoothly commit the version to the remote repository

Mixer database

  • Select for Update is a pessimistic lock
  • Controlling a database with Version is optimistic locking

3.4. Cost comparison

◆ The original cost of pessimistic lock is higher than optimistic lock, but the feature is once and for all, even if the critical section lock time is getting worse, it will not affect the cost of mutex

◆ On the contrary, although optimistic locks have a lower initial cost than pessimistic locks, they consume more and more resources if they spin for a long time or keep retrying

3.5. Usage scenarios of the two locks

◆ Pessimistic lock: suitable for concurrent write more cases, suitable for the critical region lock time is relatively long, pessimistic lock can avoid a lot of useless spin consumption, typical situation:

  • There are IO operations in the critical area
  • Critical section code complex or large loop
  • The critical zone is very competitive,

Optimistic lock: It is suitable for scenarios with few concurrent writes and most of them are read. Without lock, the read performance can be greatly improved.

4. ReentrantLock and non-reentrantLock (key)

4.1 Use Cases

4.1.1. Booking cinema seats

Code demo:

import java.util.concurrent.locks.ReentrantLock;

/** * Description: Demonstrates multithreading to book cinema seats */
public class CinemaBookSeat {

    private static ReentrantLock lock = new ReentrantLock();

    private static void bookSeat(a) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Start reserving seats.");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "Complete reservation");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public static void main(String[] args) {
        new Thread(() -> bookSeat()).start();
        new Thread(() -> bookSeat()).start();
        new Thread(() -> bookSeat()).start();
        newThread(() -> bookSeat()).start(); }}Copy the code

4.1.2 Printing Strings

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** * Description: Demo ReentrantLock basic usage, demo interrupted */
public class LockDemo {

    public static void main(String[] args) {
        new LockDemo().init();
    }

    private void init(a) {
        final Outputer outputer = new Outputer();
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                while (true) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("The wu is empty");
                }

            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                while (true) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("Big brother");
                }

            }
        }).start();
    }

    static class Outputer {

        Lock lock = new ReentrantLock();

        // String printing method, one character by one printing
        public void output(String name) {

            int len = name.length();
           // lock.lock();
            try {
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println("");
            } finally {
                //lock.unlock();}}}}Copy the code

The results

4.2 Reentrant properties

What is Reentrant: lottery story

Reentrant benefits:

  • Avoid deadlock
  • Improved encapsulation

Code demo:

import java.util.concurrent.locks.ReentrantLock;

/** * Description: demonstrates reentrant properties */
public class GetHoldCount {
    private  static ReentrantLock lock =  new ReentrantLock();

    public static void main(String[] args) { System.out.println(lock.getHoldCount()); lock.lock(); System.out.println(lock.getHoldCount()); lock.lock(); System.out.println(lock.getHoldCount()); lock.lock(); System.out.println(lock.getHoldCount()); lock.unlock(); System.out.println(lock.getHoldCount()); lock.unlock(); System.out.println(lock.getHoldCount()); lock.unlock(); System.out.println(lock.getHoldCount()); }}Copy the code

The results

import java.util.concurrent.locks.ReentrantLock;

/** * description: demonstrates recursion */
public class RecursionDemo {

    private static ReentrantLock lock = new ReentrantLock();

    private static void accessResource(a) {
        lock.lock();
        try {
            System.out.println("Resources have been processed.");
            if (lock.getHoldCount()<5) { System.out.println(lock.getHoldCount()); accessResource(); System.out.println(lock.getHoldCount()); }}finally{ lock.unlock(); }}public static void main(String[] args) { accessResource(); }}Copy the code

The results

Worker classes with ReentrantLock and ThreadPoolExecutor

IsHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread: isHeldByCurrentThread

5. Fair and unfair locks

5.1. What is fair and unfair

5.2. Why have unfair locks

5.3. Fair situation (Take ReentrantLock as an example)

If the argument is set to true when the ReentrantLock object is created, it is a fair lock: ◆ Assume that thread 1234 calls lock() sequentially

5.4. Unfair Situations (take ReentrantLock as an example)

If thread 5 happens to execute lock() when thread 1 releases the lock

◆ ReentrantLock finds that no thread is holding the lock (thread 2 has not acquired it yet because it takes time to acquire it)

◆ Thread 5 can jump the queue and get the lock directly, which is the default fair strategy of ReentrantLock, i.e. “unfair”.

5.5. Code cases: Demonstrate fair and unfair effects

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** * description: demonstrate fair and unfair two cases */
public class FairLock {

    public static void main(String[] args) {
        PrintQueue printQueue = new PrintQueue();
        Thread thread[] = new Thread[10];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(new Job(printQueue));
        }
        for (int i = 0; i < 10; i++) {
            thread[i].start();
            try {
                Thread.sleep(100);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}class Job implements Runnable {

    PrintQueue printQueue;

    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run(a) {
        System.out.println(Thread.currentThread().getName() + "Start printing");
        printQueue.printJob(new Object());
        System.out.println(Thread.currentThread().getName() + "Printed out"); }}class PrintQueue {

    private Lock queueLock = new ReentrantLock(true);

    public void printJob(Object document) {
        queueLock.lock();
        try {
            int duration = new Random().nextInt(10) + 1;
            System.out.println(Thread.currentThread().getName() + "Printing in progress, need" + duration);
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }

        queueLock.lock();
        try {
            int duration = new Random().nextInt(10) + 1;
            System.out.println(Thread.currentThread().getName() + "Printing in progress, need" + duration+"Seconds");
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ queueLock.unlock(); }}}Copy the code

A special case 5.6.

5.7. Compare the advantages and disadvantages of fair and unfair

5.8. Source code analysis

6. Shared and exclusive locks

Note This section uses the ReentrantReadWriteLock READ/write lock as an example.

6.1 What are shared locks and Exclusive locks

6.2 Functions of read/Write Locks

6.3 Read/write Lock Rules

6.4 ReentrantReadWriteLock Usage

Take buying movie tickets for example

import java.util.concurrent.locks.ReentrantReadWriteLock;

/** * Description: demonstrates read/write lock */
public class CinemaReadWrite {

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read(a) {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got read lock, reading now.");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "Release read lock"); readLock.unlock(); }}private static void write(a) {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got write lock, writing");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "Release write lock"); writeLock.unlock(); }}public static void main(String[] args) {
        new Thread(()->read(),"Thread1").start();
        new Thread(()->read(),"Thread2").start();
        new Thread(()->write(),"Thread3").start();
        new Thread(()->write(),"Thread4").start(); }}Copy the code

6.5 Interaction mode between Read Lock and Write Lock

Can boys jump the queue when men and women share toilets?

Mixer demotioneing run

◆ The choice of policy depends on the implementation of the lock. The implementation of ReentrantReadWriteLock is wise to select policy 2.

◆ Fair Lock: Queue jumping is not allowed

◆ Unfair lock

  • Write lock can cut in line at any time

  • Read locks can only be queued if the queue header is not the thread that wants to acquire the write lock

Source code analysis

Code demo:

1 Read and don’t jump the queue

import java.util.concurrent.locks.ReentrantReadWriteLock;

/** * TODO */
public class CinemaReadWriteQueue {

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read(a) {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got read lock, reading now.");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "Release read lock"); readLock.unlock(); }}private static void write(a) {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got write lock, writing");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "Release write lock"); writeLock.unlock(); }}public static void main(String[] args) {
        new Thread(()->write(),"Thread1").start();
        new Thread(()->read(),"Thread2").start();
        new Thread(()->read(),"Thread3").start();
        new Thread(()->write(),"Thread4").start();
        new Thread(()->read(),"Thread5").start(); }}Copy the code

The results

As the result of the run shows, Thread5 does not cut the queue before Thread4.

2 Reading can actually cut in line

import java.util.concurrent.locks.ReentrantReadWriteLock;

/** * Description: demonstrates an unfair and fair ReentrantReadWriteLock policy */
public class NonfairBargeDemo {

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(
            true);

    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read(a) {
        System.out.println(Thread.currentThread().getName() + "Start trying to acquire the read lock");
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got read lock, reading now");
            try {
                Thread.sleep(20);
            } catch(InterruptedException e) { e.printStackTrace(); }}finally {
            System.out.println(Thread.currentThread().getName() + "Release read lock"); readLock.unlock(); }}private static void write(a) {
        System.out.println(Thread.currentThread().getName() + "Start trying to acquire write lock");
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got write lock, writing");
            try {
                Thread.sleep(40);
            } catch(InterruptedException e) { e.printStackTrace(); }}finally {
            System.out.println(Thread.currentThread().getName() + "Release write lock"); writeLock.unlock(); }}public static void main(String[] args) {
        new Thread(()->write(),"Thread1").start();
        new Thread(()->read(),"Thread2").start();
        new Thread(()->read(),"Thread3").start();
        new Thread(()->write(),"Thread4").start();
        new Thread(()->read(),"Thread5").start();
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                Thread thread[] = new Thread[1000];
                for (int i = 0; i < 1000; i++) {
                    thread[i] = new Thread(() -> read(), "Thread created by child Thread" + i);
                }
                for (int i = 0; i < 1000; i++) { thread[i].start(); } } }).start(); }}Copy the code

Upgrade and downgrade of locks

◆ Why do we need to move up or down

Support lock degradation, do not support upgrade: code demonstration

◆ Why not support lock upgrade? A deadlock

6.6 summarize

7. Spin locks and blocking locks

7.1 Concept of spin lock

7.2 Disadvantages of spinlocks

  • If the lock is held for a long time, the spinning thread is a waste of processor resources
  • The CPU is consumed throughout the spin process, so although the initial cost of a spin lock is lower than that of a pessimistic lock, the cost increases linearly as the spin time increases

7.3 Spin lock source code analysis

import java.util.concurrent.atomic.AtomicReference;

/** * Description: spin lock */
public class SpinLock {

    private AtomicReference<Thread> sign = new AtomicReference<>();

    public void lock(a) {
        Thread current = Thread.currentThread();
        while(! sign.compareAndSet(null, current)) {
            System.out.println("Spin capture failed, try again."); }}public void unlock(a) {
        Thread current = Thread.currentThread();
        sign.compareAndSet(current, null);
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                System.out.println(Thread.currentThread().getName() + "Start trying to get a spin lock.");
                spinLock.lock();
                System.out.println(Thread.currentThread().getName() + "Got the spin lock.");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName() + "Release the spin lock."); }}}; Thread thread1 =new Thread(runnable);
        Thread thread2 = newThread(runnable); thread1.start(); thread2.start(); }}Copy the code

7.4 Application Scenarios of spin lock

  • Spinlocks are typically used on multi-core servers and are more efficient than blocking locks when concurrency is not particularly high
  • In addition, spin locks are good for situations where critical sections are small, otherwise if critical sections are large (the thread will release the lock after a long time), it is not appropriate

8. Interruptible locks: As the name implies, locks that respond to interrupts

Interruptible lock

  • In Java, synchronized is not an interruptible Lock and Lock is because tryLock(time) and lockInterruptibly both respond to interrupts.
  • If thread A is executing the code in the lock, and thread B is waiting to acquire the lock maybe because it’s waiting too long, thread B doesn’t want to wait, wants to do something else first and we can interrupt it, that’s called an interruptible lock

9. Lock optimizations

How to optimize locking and concurrency performance when we write code:

1 Shrink the synchronization code block

2 Try not to lock methods

3 Reduce the number of lock requests

4 Avoid artificial “hot Spots”

5 Do not include any more locks

6 Select an appropriate lock type or tool class