当使用Java中的ReentrantLock时,我们需要注意一些常见的问题。
1. 必须使用try-finally语句块
在使用ReentrantLock时,在临界区代码执行完毕后,必须释放锁,否则可能导致其他线程无法进入临界区。同时,在代码执行过程中,可能会抛出异常或执行return语句,这些情况也需要确保锁被正确释放。因此,我们需要使用try-finally语句块来保证锁的正确释放,如下所示:
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
2. 可重入性
ReentrantLock是可重入锁,这意味着同一线程可以多次获取锁,方法内部调用同一个ReentrantLock的lock方法不会造成死锁。在使用可重入锁时,需要确保在同一线程内,lock方法和unlock方法的调用次数相等,否则会造成死锁。
public class ReentrantLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
private int count;
public void increment() {
lock.lock();
try {
count++;
increment();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
Runnable r = () -> {
demo.increment();
};
new Thread(r).start();
new Thread(r).start();
}
}
在上述示例中,increment方法中递归调用了increment方法,每次都获取了锁,由于ReentrantLock是可重入锁,因此不会出现死锁。
3. tryLock方法可能会造成优先反转问题
在使用ReentrantLock的tryLock方法时,如果不能获取到锁,会返回false,此时线程可以继续做其他的事情,不会一直等待锁的释放。但是,由于线程调度的不确定性,tryLock方法可能会出现优先反转的问题。优先反转是指一个优先级较低的线程获得了锁导致优先级较高的线程一直无法获取锁。
public class ReentrantLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
public void lockTest() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " get lock");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
Runnable r1 = () -> {
demo.lockTest();
};
Runnable r2 = () -> {
while (true) {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " get lock");
break;
} finally {
lock.unlock();
}
}
}
};
new Thread(r1).start();
new Thread(r2).start();
}
}
在上述示例中,线程1获取锁后休眠100ms,线程2不停地尝试获取锁,如果获取到了就输出信息并退出循环。由于线程1先获取了锁并休眠了一段时间,线程2很有可能获取成功,但是在该示例中,线程2优先级更低,线程1的锁还没有释放,因此线程2会一直尝试获取锁,无法退出循环。
4. 锁的公平性和非公平性
ReentrantLock提供了公平锁和非公平锁两种实现。公平锁会按照线程的请求顺序来获取锁,避免了优先级反转的问题。但是公平锁的性能相对较低,需要线程阻塞和唤醒的代价。非公平锁则会根据锁的状态来快速获取锁,虽然存在优先级反转问题,但是性能更好。
public class ReentrantLockDemo {
private static final ReentrantLock lock = new ReentrantLock(false);
public void lockTest() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " get lock");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
Runnable r = () -> {
demo.lockTest();
};
new Thread(r).start();
new Thread(r).start();
}
}
在上述示例中,lock对象被初始化为非公平锁,多个线程获取锁时,会根据锁的状态来快速获取锁,从而提高性能。
以上是Java中ReentrantLock常见的坑,如果使用不当,可能会导致程序出现死锁或性能下降等问题。因此,在使用ReentrantLock时,需要认真考虑锁的释放、可重入性、优先反转问题,以及锁的公平性和非公平性等问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java中ReentrantLock4种常见的坑 - Python技术站