可重入锁和不可重入锁
程序员文章站
2024-01-08 08:21:34
...
锁
锁就是把代码块、资源或数据(称为临界资源)锁上,访问临界资源的时候只允许一个线程去操作,其他线程必须等待或者放弃,这是为了保证最终程序的正确运行。
不可重入锁
public class Lock {
private boolean isLocked = false; // 标识锁是否被线程获得
public synchronized void lock() throws InterruptedException {
// 如果锁被其他线程获取,则该线程等待
while(isLocked) {
wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
使用上面定义的Lock锁:
public class Test{
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
test.print();
}
Lock lock = new Lock();
public void print() throws InterruptedException {
lock.lock();
add();
lock.unlock();
}
public void add() throws InterruptedException {
lock.lock();
// do something
lock.unlock();
}
}
这段程序运行会出现死循环。当调用print()方法的时候,线程获取到锁,然后调用add()方法,线程再次尝试获取锁,被阻塞。add()方法要等print()方法执行结束才能获取锁,而print()方法要等add()方法执行结束才能释放锁,导致死锁。
当调用print()方法时,获得了锁,再未释放锁之前,这个线程就不能再调用其他方法,在其他方法中尝试获取锁。这种锁称为不可重入锁,也叫自旋锁。
可重入锁
public class Lock {
boolean isLocked = false; // 标识锁是否被线程获得
Thread lockedBy = null; // 记录获得锁的线程
int lockedCount = 0; // 记录一个线程中,锁被获取的次数
public synchronized void lock() throws InterruptedException {
Thread thread = Thread.currentThread(); // 当前尝试获取锁的线程
// 锁已经被线程获得,并且获取锁的线程不是当前线程,则当前线程等待
while(isLocked && lockedBy != thread) {
wait();
}
isLocked = true;
lockedBy = thread;
lockedCount++;
}
public synchronized void unlock() {
lockedCount--;
// 该线程中,获取锁的程序都执行了释放锁操作,线程才真正释放锁
if(0 == lockedCount) {
isLocked = false;
notify();
}
}
}
当第一个线程执行print()方法的时候,获取到了锁,lockedBy等于该线程,该线程调用add()方法时,尝试获取锁,isLocked值为true,即锁已经被占用,但是lockedBy等于该线程,所以add()方法可以获取锁,lockedCount的值此时为2。当第二个线程执行print方法的时候,isLocked值为true,并且lockedBy不等于当前线程成立,因此进入等待状态。这就是可重入锁。
可重入锁比不可重入锁多了两个属性:lockedBy表示锁被哪个线程获取,lockedCount表示一个线程内,锁被获取到的次数。根据lockedBy属性,使当前线程的不同代码块可以获取多次锁,根据lockedCount属性,执行真正释放锁的逻辑。
Java中常用的可重入锁
- synchronized
- java.util.concurrent.locks.ReentrantLock