Home  >  Article  >  Java  >  What are the common pitfalls of ReentrantLock in Java?

What are the common pitfalls of ReentrantLock in Java?

王林
王林forward
2023-05-10 18:55:141619browse

Lock Introduction

Lock is a top-level interface, and all its methods are shown in the following figure:

What are the common pitfalls of ReentrantLock in Java?

Its subclass list is as follows:

What are the common pitfalls of ReentrantLock in Java?

We usually use ReentrantLock to define its instances. The relationship between them is as shown in the figure below:

What are the common pitfalls of ReentrantLock in Java?

PS: Sync means synchronization lock, FairSync means fair lock, and NonfairSync means unfair lock.

Using ReentrantLock

Learning any skill starts with using it, so we are no exception. Let’s first take a look at the basic use of ReentrantLock:

public class LockExample {
    // 创建锁对象
    private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        // 加锁操作
        lock.lock();
        try {
            // 业务代码......
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

After ReentrantLock is created, there are two key operations:

  • Lock operation: lock()

  • Release lock operation: unlock()

## Pitfalls in ReentrantLock

1.ReentrantLock defaults to an unfair lock

Many people will think that (Especially for novice friends), the default implementation of ReentrantLock is a fair lock, but this is not the case. ReentrantLock is an unfair lock by default (this is mainly due to performance considerations),

For example, as follows This code:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 定义线程任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 加锁
                lock.lock();
                try {
                    // 打印执行线程的名字
                    System.out.println("线程:" + Thread.currentThread().getName());
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };
        // 创建多个线程
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }
}

The execution results of the above program are as follows:

What are the common pitfalls of ReentrantLock in Java?

It can be seen from the above execution results , ReentrantLock is an unfair lock by default. Because the names of threads are incremented according to the order in which they are created, if it is a fair lock, then the execution of the threads should be incremented in order. However, as can be seen from the above results, the execution and printing of the threads are out of order. This Description ReentrantLock is an unfair lock by default.

It is very simple to set ReentrantLock as a fair lock. You only need to set a true construction parameter when creating ReentrantLock.

The following code is shown:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象(公平锁)
    private static final ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) {
        // 定义线程任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 加锁
                lock.lock();
                try {
                    // 打印执行线程的名字
                    System.out.println("线程:" + Thread.currentThread().getName());
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };
        // 创建多个线程
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }
}

The execution results of the above program are as follows:

What are the common pitfalls of ReentrantLock in Java?

As can be seen from the above results, when we explicitly set the true constructor for ReentrantLock After passing the parameters, ReentrantLock becomes a fair lock, and the order in which threads acquire locks becomes orderly.

In fact, from the source code of ReentrantLock, we can also see whether it is a fair lock or an unfair lock.

Part of the source code of ReentrantLock is implemented as follows:

 public ReentrantLock() {
     sync = new NonfairSync();
 }
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

From the above source code, you can It can be seen that ReentrantLock will create an unfair lock by default. If the value of the construction parameter is explicitly set to true during creation, it will create a fair lock.

2. Release the lock in finally

Be sure to remember to release the lock when using ReentrantLock, otherwise the lock will be occupied forever, and other threads using the lock will be permanently occupied. Waiting , so when we use ReentrantLock, we must release the lock in finally, so as to ensure that the lock will be released.

Counterexample

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 加锁操作
        lock.lock();
        System.out.println("Hello,ReentrantLock.");
        // 此处会报异常,导致锁不能正常释放
        int number = 1 / 0;
        // 释放锁
        lock.unlock();
        System.out.println("锁释放成功!");
    }
}

The execution results of the above program are as follows:

What are the common pitfalls of ReentrantLock in Java?## From the above It can be seen from the results that when an exception occurs, the lock is not released normally, which will cause other threads using the lock to be in a waiting state permanently.

Positive example

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 加锁操作
        lock.lock();
        try {
            System.out.println("Hello,ReentrantLock.");
            // 此处会报异常
            int number = 1 / 0;
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释放成功!");
        }
    }
}

The execution results of the above program are as follows:

What are the common pitfalls of ReentrantLock in Java? From It can be seen from the above results that although an exception occurs in the method, it does not affect the release operation of the ReentrantLock lock, so that other threads using this lock can obtain and run normally.

3. The lock cannot be released multiple times

The number of lock operations and the number of unlock operations must correspond one to one, and a lock cannot be released multiple times, because this will Causes the program to report an error.

Counter example

One lock corresponds to two unlock operations, causing the program to report an error and terminate execution. The sample code is as follows:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 加锁操作
        lock.lock();

        // 第一次释放锁
        try {
            System.out.println("执行业务 1~");
            // 业务代码 1......
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释锁");
        }

        // 第二次释放锁
        try {
            System.out.println("执行业务 2~");
            // 业务代码 2......
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释锁");
        }
        // 最后的打印操作
        System.out.println("程序执行完成.");
    }
}

The execution results of the above program are as follows:

What are the common pitfalls of ReentrantLock in Java?# As can be seen from the above results, when the second unlock is executed, the program reports an error and terminates execution. , causing the code after the exception to not execute normally.

4.lock 不要放在 try 代码内

在使用 ReentrantLock 时,需要注意不要将加锁操作放在 try 代码中,这样会导致未加锁成功就执行了释放锁的操作,从而导致程序执行异常。

反例

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        try {
            // 此处异常
            int num = 1 / 0;
            // 加锁操作
            lock.lock();
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释锁");
        }
        System.out.println("程序执行完成.");
    }
}

以上程序的执行结果如下: 

What are the common pitfalls of ReentrantLock in Java?

 从上述结果可以看出,如果将加锁操作放在 try 代码中,可能会导致两个问题:

  • 未加锁成功就执行了释放锁的操作,从而导致了新的异常;

  • 释放锁的异常会覆盖程序原有的异常,从而增加了排查问题的难度。

The above is the detailed content of What are the common pitfalls of ReentrantLock in Java?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete