What is reentrant?

Re-entrant -Lock translates to reentrant Lock. It’s called that because the lock can be re-entered. Of course, this repetition is only limited to one thread. Look at the code below. After F1 locks, F2 can still get the lock and execute it, because they belong to the main thread.

public class Main {

    static class Test {
        private final ReentrantLock lock = new ReentrantLock();

        public void f1(a) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName());
                f2();
            } finally{ lock.unlock(); }}public void f2(a) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName());
            } finally{ lock.unlock(); }}}public static void main(String[] args) {
        Test test = newTest(); test.f1(); }}Copy the code

Reentrant vs. synchronized

Reentrant locking is a complete substitute for the synchronized keyword. In earlier versions of JDK 5.0, reentrant locking performed much better than synchronized, but starting in JDK 6.0, the JDK has made a number of optimizations on Synchronized so that the performance difference is not significant.

Implement num++ in two ways:

Reentrant lock

public class Main {

    static int num = 0;
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[32];
        Runnable runnable = () -> {
            for (int i = 0; i < 10000; i++) {
                lock.lock();
                try {
                    num++;
                } finally{ lock.unlock(); }}};for (int i = 0; i < 32; i++) {
            ts[i] = new Thread(runnable);
            ts[i].start();
        }
        for (int i = 0; i < 32; i++) { ts[i].join(); } System.out.println(num); }}Copy the code

synchronized

public class Main {

    static int num = 0;
    static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[32];
        Runnable runnable = () -> {
            for (int i = 0; i < 10000; i++) {
                synchronized(obj) { num++; }}};for (int i = 0; i < 32; i++) {
            ts[i] = new Thread(runnable);
            ts[i].start();
        }
        for (int i = 0; i < 32; i++) { ts[i].join(); } System.out.println(num); }}Copy the code

In response to interrupt

With synchronized, if a thread is waiting for a lock, there are only two outcomes: it either acquires the lock and continues to execute, or it waits. With a reentrant lock, another possibility is that the thread can be interrupted.

Simulate a deadlock scenario: two threads need to acquire lock1, lock2 has two locks, thread T1 has acquired lock1 first, thread T2 has acquired lock2 first, and then they start to wait indefinitely for each other to relinquish the lock. However, if thread T2 is interrupted and the lock held by thread T2 is released, t1 will run normally:

public class Main {

    static class IntLock implements Runnable {
        private static ReentrantLock lock1 = new ReentrantLock();
        private static ReentrantLock lock2 = new ReentrantLock();
        private int lock;

        public IntLock(int lock) {
            this.lock = lock;
        }

        @Override
        public void run(a) {
            try {
                if (lock == 1) {
                    lock1.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                        lock2.lockInterruptibly();
                        try {
                            System.out.println(T1 successfully acquired two locks);
                        } finally{ lock2.unlock(); }}catch (InterruptedException e) {
                        System.out.println("T1 is interrupted.");
                        Thread.currentThread().interrupt();
                    } finally{ lock1.unlock(); }}else {
                    lock2.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                        lock1.lockInterruptibly();
                        try {
                            System.out.println("T2 successfully acquired two locks");
                        } finally{ lock1.unlock(); }}catch (InterruptedException e) {
                        System.out.println("T2 is interrupted");
                        Thread.currentThread().interrupt();
                    } finally{ lock2.unlock(); }}}catch(InterruptedException e) { Thread.currentThread().interrupt(); }}}public static void main(String[] args) throws InterruptedException {
        IntLock r1 = new IntLock(1);
        IntLock r2 = new IntLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000); t2.interrupt(); }}Copy the code

Lock request wait time limit

The tryLock() method takes two arguments, one for the wait time and one for the unit of time. It can also run without arguments. In this case, the current thread attempts to acquire the lock, and if the lock is not occupied by another thread, the lock request is successful and immediately returns true. If the lock is occupied by another thread, the current thread does not wait, but returns false immediately.

public class Main {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Runnable target = () -> {
            try {
                if (lock.tryLock(1, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + " get lock success");
                    Thread.sleep(4000);
                } else {
                    System.out.println(Thread.currentThread().getName() + " get lock failed"); }}catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                if(lock.isHeldByCurrentThread()) { lock.unlock(); }}}; Thread t1 =new Thread(target);
        Thread t2 = newThread(target); t1.start(); t2.start(); }}Copy the code

Fair lock

In most cases, lock claims are unfair. That is, thread 1 first requests lock A, and thread 2 then requests lock A. So when lock A is available, can thread 1 acquire the lock or thread 2 acquire the lock? That’s not necessarily true. The system just randomly selects a lock from the wait queue. Therefore, its fairness cannot be guaranteed. This is like buying a ticket without waiting in line, everyone is in a hubbub around the ticket window, the conductor is too busy to care who first who last, just find someone to give the ticket. A fair lock, on the other hand, guarantees first come, first served, and last come, last served in chronological order. One of the key features of the fair lock is that it does not cause hunger. As long as you wait in line, you can eventually wait for resources. If we use the synchronized keyword for lock control, the resulting lock is unfair. The reentrant lock allows us to set its fairness. It has the following constructor:

public ReentrantLock(boolean fair)
Copy the code

When fair is true, the lock is fair. Fair locks look beautiful, but to achieve fair locks, the system must maintain an ordered queue, so the implementation cost of fair locks is relatively high, relatively low performance, therefore, by default, the lock is not fair. If there are no special requirements, there is no need to use fair locks. Fair and unfair locks also have very different thread scheduling performance. The following code is a good way to highlight fair locking:

public class Main {

    private static ReentrantLock fairLock = new ReentrantLock(true);

    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            while(! Thread.currentThread().isInterrupted()) { fairLock.lock();try {
                    System.out.println(Thread.currentThread().getName() + "Get lock");
                } finally{ fairLock.unlock(); }}}; Thread t1 =new Thread(target);
        Thread t2 = new Thread(target);
        t1.start();
        t2.start();
        Thread.sleep(1000); t1.interrupt(); t2.interrupt(); }}Copy the code

As you can see from the results, in most cases the two threads run alternately.

. Thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock  Thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock thread-1 obtains the lock...Copy the code

However, with an unfair lock, you will find that the same thread has acquired the lock many times in a row.

. Thread0 acquires the lock thread0 acquires the lock thread0 acquires the lock thread0 acquires the lock thread0 acquires the lock... Thread-1 acquired the lock thread-1 acquired the lock Thread-1 acquired the lock Thread-1 acquired the lock Thread-1 acquired the lock thread-1 acquired the lock thread-1 acquired the lock...Copy the code