Java knowledge needed for work (JUC Lock Learning Paper)

JUC is a part of my job that I don't use very much, but I often get asked about it in a helpless interview, so I'll learn about JUC today.

JUC is all called Java. Util. The concurrent package is a toolkit for Java, so why is JUC a frequent question for interviews? Because JUC includes multithreading, atomicity, locks and other heavy content, it can be used to examine a programmer's understanding of Java technology.

1. Review multithreading

Interview 1: Can Java create threads?

Answer: No, Java creation threads call the underlying local native method and C++ to create threads.

Query PC CPU Cores

package com.test.rabbitmq.test;

/**
 * @author ME
 * @date 2022/2/5 0:41
 */
public class Test {
    public static void main(String[] args) {
        System.out.println("Of this computer CPU Number of cores" + Runtime.getRuntime().availableProcessors() + "nucleus");
    }
}

Threads have several States

NameMeaning
NEWnewborn
RUNNABLEFunction
BLOCKEDblock
WAITINGwait for
TIMED_WAITINGTimeout Wait
TERMINATEDtermination

The difference between wait and sleep methods

When I was learning multithreading, I talked about the difference between the two, and I'll repeat here

1. From different classes, wait method from Object class, sleep method from Thread class

Avoid using the sleep method directly by using the following code when sleeping at work

// Sleep one day
TimeUnit.DAYS.sleep(1);

// Sleep for two seconds
TimeUnit.SECONDS.sleep(2);

2. Lock release, wait method releases lock, sleep method does not release lock

3. The wait method must be used in the synchronous code block, otherwise IllegalMonitorStateException will be reported, and the sleep method can be used anywhere

Synchronized Differentiates Actual Warfare from Lock

Let's first create a business logic class for buying tickets

package com.test.rabbitmq.test;

/**
 * @author ME
 * @date 2022/2/5 15:25
 * Ticket Selling Category
 */
public class Ticket {
    // Set up a total of 30 tickets
    private int ticketNum = 30;

    // Set up ticket selling methods
    public void sell() {
        // We can only sell tickets when our remaining votes are greater than 0
        if (ticketNum > 0) {
            // Reduce the number of votes first, then add more, which adds extra logic and makes errors easy
            ticketNum--;
            // Reduce the number of votes after each ticket is sold and print the remaining number of votes
            System.out.println(Thread.currentThread().getName() + "Sell No." + (ticketNum + 1) + "Tickets, Surplus:" + (ticketNum) + "Tickets");
        }
    }
}

Then we create a multithreaded call

package com.test.rabbitmq.test;

/**
 * @author ME
 * @date 2022/2/5 15:25
 */
public class MyLockTest {
    // Main method for execution
    public static void main(String[] args) {
        // Object to create ticket
        Ticket ticket = new Ticket();
        // Thread A has a total of 30 people to buy tickets
        new Thread(() -> { for (int i = 0; i < 30; i++) ticket.sell();}, "A").start();
        // Thread B has 30 people to buy tickets
        new Thread(() -> { for (int i = 0; i < 30; i++) ticket.sell();}, "B").start();
        // Thread C has 30 people to buy tickets
        new Thread(() -> { for (int i = 0; i < 30; i++) ticket.sell();}, "C").start();
    }
}

The results of this execution are as follows, which results in resource insecurity due to data sharing between threads.

Then we try again after adding the Synchronized keyword to the ticket sales category

package com.test.rabbitmq.test;

/**
 * @author ME
 * @date 2022/2/5 15:25
 * Ticket Selling Category
 */
public class Ticket {
    // Set up a total of 30 tickets
    private int ticketNum = 30;

    // Set up the method of selling tickets, add synchronized keyword
    public synchronized void sell() {
        // We can only sell tickets when our remaining votes are greater than 0
        if (ticketNum > 0) {
            // Reduce the number of votes first, then add more, which adds extra logic and makes errors easy
            ticketNum--;
            // Reduce the number of votes after each ticket is sold and print the remaining number of votes
            System.out.println(Thread.currentThread().getName() + "Sell No." + (ticketNum + 1) + "Tickets, Surplus:" + (ticketNum) + "Tickets");
        }
    }
}

The results of the execution are as follows, and you will find that the execution is normal and orderly. The key is that the synchronized keyword adds synchronization, and only one thread can use a resource at a time.

Next we'll use lock locks

This is a new concept, there is a lock in JUC, for lock, in our java. Util. Concurrent. Under the locks package.

Known implementation classes are:

ReentrantLockRe-lockable
ReentrantReadWriteLock.ReadLockRead Lock
ReentrantReadWriteLock.WriteLockWrite lock

Let's check the source code for ReentrantLock again

So what is a fair lock and what is an unfair lock?

fair lockFirst come, first come, first execute
Unfair LockQueuing is possible and performance is much higher than fair locks, which are unfair locks used by default by synchronized locks and lock locks in JUC

Lock lock code is as follows

package com.test.rabbitmq.test;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author ME
 * @date 2022/2/5 15:25
 * Ticket Selling Category
 */
public class Ticket {
    // Set up a total of 30 tickets
    private int ticketNum = 30;
    // Create Lock Object Outside Method
    ReentrantLock lock = new ReentrantLock();
    // Set up ticket selling methods
    public void sell() {
        // Lock inside method, followed by tryCatch
        lock.lock();
        try {
            // We can only sell tickets when our remaining votes are greater than 0
            if (ticketNum > 0) {
                // Reduce the number of votes first, then add more, which adds extra logic and makes errors easy
                ticketNum--;
                // Reduce the number of votes after each ticket is sold and print the remaining number of votes
                System.out.println(Thread.currentThread().getName() + "Sell No." + (ticketNum + 1) + "Tickets, Surplus:" + (ticketNum) + "Tickets");
            }
        }catch (Exception e) {
        }finally {
            // Must be unlocked
            lock.unlock();
        }
    }
}

The results are as follows, as does the synchronized keyword

So the question arises, what is the difference between the two?

1.synchronized is a keyword in Java; Lock is a Java class

2.synchronized cannot acquire the state of the lock because it is automatic; Lock can determine if a lock has been acquired

3.synchronized automatically releases the lock because it is automatic; Lock must release the lock manually, if not it will cause a deadlock

4.synchronized One thread gets the lock and the other threads wait all the time; Lock can attempt to acquire a lock through the tryLock() method and do other things

5.synchronized locks are re-entrainable, uninterruptable, and unfair; Lock is a reentrant lock and can determine the state of the lock. Fair and unfair locks can be set by themselves

6.synchronized lock is not flexible; Lock locks are more flexible

Producer-consumer issues

We use the synchronized and wait() and notify() methods to communicate between threads with the following code

Let's first understand the difference between a notify() and a notifyAll() method

notify() is a random wake-up of a thread; notifyAll() is to wake up all waiting threads to contend for locks

Notfy() performs better than notifyAll().

So when do you use the notifyAll() method?

This has to mention the relationship between the lock pool and the wait pool. When we start threads, threads enter the lock pool to contend for locks. When we execute the wait() method, threads entering the wait pool do not participate in the lock contention. Only when the notify() or notifyAll() method is executed, threads wake up from the wait pool to join the lock pool to contend for locks. Once there are no threads in the lock pool, the thread waked up by the notify() method also goes to the wait() method, where all threads are waiting, causing a deadlock.

Let's start by creating two methods, which you need to understand

package com.test.rabbitmq.lockTest;

/**
 * @author ME
 * @date 2022/2/5 20:26
 */
public class A {
    private int number = 0;
    // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            // Make Thread Wait
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
        // Wake up other threads
        this.notifyAll();
    }
    // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            // Make Thread Wait
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
        // Wake up other threads
        this.notifyAll();
    }
}

Then we start these two methods with multithreading

package com.test.rabbitmq.lockTest;

/**
 * @author ME
 * @date 2022/2/5 20:31
 */
public class Test {
    public static void main(String[] args) {
        A a = new A();
        // Thread A
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) a.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        // Thread B
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) a.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

The result is as follows, which enables communication between threads

But let's add two more threads, with the following code

package com.test.rabbitmq.lockTest;

/**
 * @author ME
 * @date 2022/2/5 20:31
 */
public class Test {
    public static void main(String[] args) {
        A a = new A();
        // Thread A
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) a.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        // Thread B
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) a.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();

        // Thread C
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) a.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "C").start();

        // Thread D
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) a.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "D").start();
    }
}

The results after execution are as follows

It's because there's a fake wake-up problem

if only once, while still loops while waiting

Code if succeeded after replacing while

package com.test.rabbitmq.lockTest;

/**
 * @author ME
 * @date 2022/2/5 20:26
 */
public class A {
    private int number = 0;
    // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            // Make Thread Wait
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
        // Wake up other threads
        this.notifyAll();
    }
    // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            // Make Thread Wait
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
        // Wake up other threads
        this.notifyAll();
    }
}

The results are as follows

The code above uses the synchronized and wait() and notify() methods to communicate between threads, so is there an equivalent alternative in JUC? The answer is yes, if you replace synchronized with Lock in JUC, then other methods can also be replaced with await() and signal().

Below is a comparison of the two codes

package com.test.rabbitmq.lockTest;

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

/**
 * @author ME
 * @date 2022/2/5 20:26
 */
public class A {
    private int number = 0;

    // Use synchronized + wait + notify mode
    // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            // Make Thread Wait
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
        // Wake up other threads
        this.notifyAll();
    }
    // Use lock lock + await + signal
    // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads
    // Create Lock Object
    ReentrantLock lock = new ReentrantLock();
    // Create object monitor objects that signal() can wake up a specified thread compared to notify()
    Condition condition = lock.newCondition();
    
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // Make Thread Wait
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
            // Waking up other threads is the same as notify-notifyAll, JUC has signal-signalAll
            condition.signalAll();
        }catch (Exception e) {
        }finally {
            lock.unlock();
        }
    }
}

How the condition class specifies thread wakeup

We learned above that notify() can only wake up randomly, and conditions can wake up as specified, so how does it work? The code is as follows, specifying wake-up after distinguishing by creating multiple Condition classes

package com.test.rabbitmq.lockTest;

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

/**
 * @author ME
 * @date 2022/2/5 20:26
 */
public class A {
    private int number = 0;
    // Create Lock Object
    ReentrantLock lock = new ReentrantLock();
    // Create increment object monitor object
    Condition incrementCondition = lock.newCondition();
    // Create decrement object monitor object
    Condition decrementCondition = lock.newCondition();

    // Use lock lock + await + signal
    // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0) {
                // The object monitor of this method performs thread wait
                incrementCondition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
            // Waking up other threads is the same as notify-notifyAll, JUC has signal-signalAll
            // Specify wake decrement method
            decrementCondition.signal();
        }catch (Exception e) {
        }finally {
            lock.unlock();
        }
    }

    // Use lock lock + await + signal
    // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // The object monitor of this method performs thread wait
                decrementCondition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number);
            // Waking up other threads is the same as notify-notifyAll, JUC has signal-signalAll
            // Specify wake up increment method
            incrementCondition.signal();
        }catch (Exception e) {
        }finally {
            lock.unlock();
        }
    }
}

Common problem solving for locks

1. The synchronized method of the same object is called in multiple threads, starting with the first execution

Reason: The object of a synchronized lock is the calling object of a method. Whoever calls this method locks whoever. If two objects are created, they will not affect each other, or the execution of a common method will not have to wait. However, if the same object is used, even if the sleep() method is used to sleep, other threads will have to wait for the thread's sleep time to finish execution before executing it.

When synchronized locks are static methods, the lock is a class object, locking the class. He differs from the lock normal method in that when synchronized locks the method, even if two objects are created, they affect each other because they share a class class class.

In the same class, when synchronized locks a class, it does not interfere with its instance object, and the instance object locks normally.

Keywords: Java Interview JUC lock synchronized

Added by UQ13A on Sat, 05 Feb 2022 19:04:49 +0200