本文内容如有错误、不足之处,欢迎技术爱好者们一同探讨,在本文下面讨论区留言,感谢。
简介
可重入锁在Java中有synchronize和ReentrantLock,其中synchronize是在JVM中进行可重入控制,ReentrantLock是在代码中实现可重入控制。
在Java 5.0中,增加了一个称为ReentrantLock的新功能,以增强内部锁定功能。在此之前,“synchronized" 和 "volatile”是实现并发的手段。
public synchronized void doAtomicTransfer(){
//进入同步代码块 获取对此对象的锁定。
operation1()
operation2();
} // 退出同步代码块, 释放(release)对此对象的锁定。
同步使用内部锁或监视器。Java中的每个对象都有一个与之关联的固有锁。每当线程尝试访问同步的块或方法时,它都会获取该对象的固有锁定或监视器。在使用静态方法的情况下,线程获取对类对象的锁定。就编写代码而言,内在锁定机制是一种干净的方法,对于大多数用例而言,它是相当不错的。
那么,为什么我们需要显式锁的附加功能?
内部锁定机制可能具有一些功能限制,例如:
- 无法中断等待获取锁的线程(lock Interruptibly)。
- 如果不愿意永远等待,就无法尝试获取锁(try lock)。
- 无法实现非块结构的锁定规则,因为必须在获取它们的同一块中释放固有锁。
ReentrantLock
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
.....
中断锁获取
可中断的锁获取允许在可取消的活动中使用锁。
该的lockInterruptibly方法可以让我们尝试,同时可中断获取锁。因此,基本上,它允许线程立即响应从另一个线程发送给它的中断信号。
例子:假设有一条共享线路来发送消息。希望以这样一种方式设计它:如果另一个线程来了并中断当前线程,则锁应释放并执行退出或关闭操作以取消当前任务。
public boolean sendOnSharedLine(String message) throws InterruptedException{
lock.lockInterruptibly();
try{
return cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String message){
.......
可重入设计的意义
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。
这样可以避免每次获取锁之后,在操作资源后,释放锁再获取锁,每次释放锁再获取锁的调度切换是很耗资源。
避免子类改写了父类的 synchronized 方法,调用子类时发生死锁。
不可重入锁
class UnReentantLock{
private AtomicReference<Thread> owner = new AtomicReference<>();
/**
* 获取锁
*/
public void lock(){
Thread current = Thread.currentThread();
// 自旋锁实现,同样可以使用do while
//for (; ; ) {
// if (!owner.compareAndSet(null, current)) {
// return ;
// }
//}
do {
} while (!owner.compareAndSet(null, current));
return;
}
/**
* 释放锁
*/
public void unLock(){
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
这里使用了原子引用和CAS
CAS的原理是Unsafe类和自旋锁,上面已经展示自旋锁如何使用,下面图片是Unsafe类中的一个native方法的c++实现,可以查看 JVM 本地方法栈(native method stack)解释
不可重入改为可重入
可重入锁实现是通过计数器来实现可重入多次。
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock();
try{
...
}finally{
lock.unlock();
lock.unlock();
}
上面的代码可以说明,加了几次lock()就要执行几次unlock();
下面是改造代码:
借鉴:https://blog.csdn.net/wuseyukui/article/details/72312574
class NumberReentrantLock{
private AtomicReference<Thread> owner = new AtomicReference<>();
private int lockNumber = 0;
public void lock(){
Thread current = Thread.currentThread();
// 使用== 表示是同一个引用
if (current == owner.get()) {
lockNumber++;
return;
}
do {
} while (!owner.compareAndSet(null, current));
}
public void unlock(){
Thread current = Thread.currentThread();
if (current == owner.get()) {
if (lockNumber == 0) {
owner.compareAndSet(current, null);
}else{
lockNumber--;
}
}
}
}