Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

In the previous article Java multi-threaded (three) multi-threaded insecurity typical example – Nuggets (juejin. Cn) I wrote in the multi-threaded environment often encounter multi-threaded insecurity, and cited three typical examples, After I in the last article Java multithreading (four) to solve multithreading security – synchronized – nuggets (juejin. Cn) in the explanation of a solution to multithreading insecure method, in this article introduces a locking mechanism ReentrantLock, he can also play a role in protecting multithreading security.

The use of already

The following is the format used by ReentrantLock. Be sure to unlock ReentrantLock after using it.

ReentrantLocklock = new ReentrantLock();
try{
	lock.lock();// Lock
}finally{
    lock.unlock();
}
Copy the code

Here is a simple example to see how to use this lock. In the example of buying tickets, three threads want to buy tickets, a total of 60 tickets, using ReentrantLock to ensure thread safety.

class Ticket implements Runnable{

    private int alltickets = 60;
    private boolean flag = true;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run(a) {
        while(alltickets>0) {
                if (this.flag == true) {
                    try {
                        this.lock.lock();
                        Thread.sleep(300);
                        buy();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        this.lock.unlock(); }}else {
                    break; }}}public void buy(a) throws InterruptedException {

        if(this.alltickets<=0)
        {
            System.out.println("There are no tickets left."+Thread.currentThread().getName());
            this.flag = false;
            return;
        }

        else
        {
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName()+"Bought the number"+this.alltickets--+"Ticket"); }}}public class testThread {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Ticket t = new Ticket();
        new Thread(t, "Xiao Hua").start();
        new Thread(t, "Xiao Ming").start();
        new Thread(t, "Cow").start(); }}Copy the code

The code above produces the following output

ReentrantLock is different from synchronized

  1. Synchronized is a Java keyword, and ReentrantLock is a Java class that requires lock() and unlock() methods combined with try/finally blocks

  2. Synchronized cannot determine the state of a lock, whereas ReentrantLock can determine whether a lock has been acquired

  3. Synchronized automatically releases the lock, while ReentrantLock must manually release the lock, and deadlocks occur if it does not release the lock

  4. Synchronized can wait without releasing the lock, but ReentrantLock can attempt to unlock it

  5. Synchronized is a ReentrantLock and cannot be broken, but ReentrantLock can be broken and can be either fair or unjust

The characteristics of the already

  • Reentrant lock
  • Fair lock, unfair lock
  • interruptible
  • Can time

ReentrantLock source code parsing

After the lock.lock() function is executed, acquire(int arg) is entered, where a lock is first attempted in tryAcquire(), and if the lock attempt fails, the thread needs to be queued and blocked.

public final void acquire(int arg) {
    if(! tryAcquire(arg) &&// Try locking first
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// If the node is not locked, the new node is queued and blocked
        selfInterrupt();
}
Copy the code

TryAcquire (int acquires) ReentrantLock (tryAcquire(int acquires)) ReentrantLock (int acquires) The difference between a fair lock and an unfair lock is that for a fair lock, when a thread is blocked by the lock, the thread will queue up, and when the lock is released, the thread will give the lock to the next thread in sequence.

The tryAcquire(int acquires) function is an example of a fair lock. If the current thread is the first thread in the queue, the tryAcquire(int acquires) function is an example of a fair lock. The state value of the lock is changed to 1 by the CAS optimistic lock mechanism. The value of the optimistic lock is changed to 1 by the CAS optimistic lock mechanism. More than one thread detects that the lock is idle and there are no other threads on the queue, and then assigns the lock to the current thread. If it is found that the lock is not free, the state value of the lock needs to be changed and an attempt to acquire the lock is returned. The value of state should be the multiple of the lock, for example, if the lock() function is called three times, the value of state is 3.

protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {/ / lock idle
            if(! hasQueuedPredecessors() &&// Because it is a fair lock, check whether there are threads queueing before
                compareAndSetState(0, acquires)) {// Try to modify state
                setExclusiveOwnerThread(current);// Change the thread that the current lock belongs to
                return true; }}else if (current == getExclusiveOwnerThread()) {// The lock is not free. The thread that acquired the lock is the current thread
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);// change the value of state, related to reentrant lock, 1,2,3,4......
            return true;// Add to lock
        }
        return false; }}Copy the code

In the case of a fair lock, if the current thread fails in its attempt to acquire the lock, the thread is made a new node in the wait queue and added to the wait queue, which is what the following code does. The purpose of the for loop in the code is to make every incoming thread successfully queue in the case of multiple threads.

private Node addWaiter(Node mode) {
    Node node = new Node(mode);// Create an object

    for (;;) {// Ensure that the current Node object is enqueued, otherwise the loop will continue
        Node oldTail = tail;
        if(oldTail ! =null) {
            node.setPrevRelaxed(oldTail);
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                returnnode; }}else {
            initializeSyncQueue();// Initialize the queue if there is nothing in it}}}Copy the code

Once the new node is placed on the queue, the next step is to block the thread.

final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {// If it is the first queued, try again to obtain the lock
                setHead(node);
                p.next = null; // help GC
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node ))// Change the waitState of the previous node
                interrupted |= parkAndCheckInterrupt();// If unpark, the loop must re-enter to see if it is the first queued thread}}catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();/ / interrupt
        throwt; }}Copy the code

ReentrantReadWriteLock lock

The ReentrantLock class is completely mutually exclusive, with only one thread executing the tasks following the reentrantLock. lock() method at a time. This ensures thread-safe simultaneous writing of instance variables, but is very inefficient. Therefore, the JDK provides a read and write lock class, ReentrantReadWriteLock, which can be used to perform read operations without synchronization, improving the running speed and efficiency. Read/write locks have two types of locks: one is related to read operations, also known as shared locks. The other is a write-related lock, also known as an exclusive lock. Read locks are not mutually exclusive, read locks are mutually exclusive with write locks, and write locks are mutually exclusive with write locks. Therefore, whenever a write lock occurs, the effect of mutual exclusion synchronization will occur. The read operation reads the value of the instance variable, and the write operation writes the value to the instance variable.

Already drawback

As mentioned above, ReentrantLock is inefficient. Only one thread is executing the tasks following the reentrantLock. lock() method at a time.

class TestReentrantLock implements Runnable{
    private ReentrantLock lock = new ReentrantLock();

    private String name = "abc";

    @Override
    public void run(a) {
        lock.lock();
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"Thread entry time:" + System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName()+""+name);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"Thread running time:" + (endTime - startTime ) + "ms"); lock.unlock(); }}public class ReadLock {
    public static void main(String[] args) {
        TestReentrantLock t = new TestReentrantLock();
        new Thread(t,"ReentrantLock线程1").start();
        new Thread(t,"ReentrantLock线程2").start(); }}Copy the code

ReentrantReadWriteLock Read share

ReentrantReadWriteLock can be used to read and share data. In this example, two reading threads enter at almost the same time, which greatly improves efficiency. Readlock.readlock () and readlock.readlock ().unlock() are used to lock and unlock readLock.readlock ().

class TestReentrantReadWriteLock implements Runnable{
    private ReentrantReadWriteLock readLock = new ReentrantReadWriteLock();
    private String name = "abc";


    @Override
    public void run(a) {
        readLock.readLock().lock();
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"Thread entry time:" + System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName()+""+name);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"Thread running time:" +  (endTime - startTime ) + "ms"); readLock.readLock().unlock(); }}public class ReadLock {
    public static void main(String[] args) {
        TestReentrantReadWriteLock t1 = new TestReentrantReadWriteLock();
        new Thread(t1,"ReentrantReadWriteLock thread 1").start();
        new Thread(t1,"ReentrantReadWriteLock thread 2").start(); }}Copy the code

ReentrantReadWriteLock Writes mutually exclusive data

In addition to read locks, ReentrantReadWriteLock also has write locks. Using write locks, ReentrantReadWriteLock becomes code that allows only one thread to execute methods after lock() at a time.

class TestReentrantReadWriteLock implements Runnable{
    private ReentrantReadWriteLock readLock = new ReentrantReadWriteLock();
    private int count = 0;


    @Override
    public void run(a) {
        readLock.writeLock().lock();
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"Thread entry time:" + System.currentTimeMillis());
        count ++;
        System.out.println(Thread.currentThread().getName()+""+count);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"Thread running time:" +  (endTime - startTime ) + "ms"); readLock.writeLock().unlock(); }}public class ReadLock {
    public static void main(String[] args) {
        TestReentrantReadWriteLock t1 = new TestReentrantReadWriteLock();
        new Thread(t1,"ReentrantReadWriteLock thread 1").start();
        new Thread(t1,"ReentrantReadWriteLock thread 2").start(); }}Copy the code