对于锁的可重入锁来说,意味着某线程获取锁之后,该线程就可以进入任何一个该锁所同步着的代码块!!! 先举一个反面例子,非可重入锁!Java提供的sun.misc.Lock
就是典型的非可重入锁。通过其源码可以看出来:
public class Lock {
private boolean locked = false;
//获取锁,获取成功之后设置locked=true
public final synchronized void lock() throws InterruptedException {
while (this.locked) {
this.wait();
}
this.locked = true;
}
//释放锁
public final synchronized void unlock() {
this.locked = false;
this.notifyAll();
}
}
sun.misc.Lock
提供的加锁和释放锁的功能很简单,通过lock()方法加锁,unlock方法加锁.所以简单的写一个demo验证下其非可重入性:
public class LockTest implements Runnable {
// 定义一把锁
private Lock lock = new Lock();
public void run() {
// 加锁
lock();
//再次加锁
lock();
PrintUtils.println(Thread.currentThread().getName() + " 开始工作五秒钟。。。。。");
ThreadUtils.sleep(5000);
PrintUtils.println(Thread.currentThread().getName() + "工作完毕,释放锁-");
lock.unlock();
}
private void lock() {
try {
lock.lock();
PrintUtils.println(Thread.currentThread().getName() + "-获取到了锁--");
} catch (InterruptedException e) {
}
}
public static void main(String args[]) {
LockTest test = new LockTest();
Thread a = new Thread(test);
a.setName("线程A");
a.start();
}
}
如上所示,我们在LockTest这个Runnable的run方法里面两次调用了lock()方法,此时因为在第一次调用lock()方法的时候已经将Lock对象的locked标志位true,所以再次调用lock()方法的时候while (this.locked)
条件就为true,从而使得Thread 在在本来已经获取锁的情况下,再次wait而阻塞,从而造成死锁。简直就是WTF.
当然有方法解决这种情况,这就是引入了可重入锁的概念,其最大的作用是避免死锁!简单的说一下其实现思路,我们可以通过Thread.currentThread()
来获取当前执行的线程,所以我们在调用lock()方法加锁的时候可以用变量lockOwner 来记录哪一个Thread获取到了锁!当一个线程A调用lock()方法成功获取到了锁,然后A再次多次调用lock()方法的时候因为已经获取了锁也就是lockOwner==A,那么就可以继续执行而不必阻塞。所以我们可以对sun.misc.Lock
来进行改造,改造后的代码如下:
public class ReentrantLock {
//是否加锁标志
private boolean locked = false;
//一个线程加锁的次数,同一个线程每调用一次lock就++
private int lockedCount = 0;
//当前获取所得线程
private Thread lockOwner = null;
public ReentrantLock() {
}
public final synchronized void lock() throws InterruptedException {
//获取当前的线程
Thread currentThread = Thread.currentThread();
// 如果其他线程已经获取了锁且当前的线程不是获取锁的那个线程
while (this.locked && currentThread != lockOwner) {
this.wait();
}
lockedCount++;
this.locked = true;
lockOwner = currentThread;
}
public final synchronized void unlock() {
Thread currentThread = Thread.currentThread();
if (currentThread != lockOwner) {
return;
}
lockedCount--;
if (lockedCount == 0) {
this.locked = false;
lockOwner = null;
this.notifyAll();
}
}
}
在博主之前的博客java线程知识点拾遗(CAS),简单写了CAS的实现,读过这篇文章的童鞋应该也能注意到 那个CAS也是不可以重入的,现在我们将其改造为可重入的锁!思路也很简单,CAS是基于比较和替换的,那么如果一个线程已经获取了锁,再次调用lock的时候不让其进行比较和替换就可以了:
public class CasReentrantLock {
private AtomicReference lockedThread = new AtomicReference();
private int lockedCount = 0;
// 模拟自旋
public void lock() {
Thread current = Thread.currentThread();
Thread lockedBy = lockedThread.get();
// 如果当前线程和之前获取所得线程是同一个线程,那么久直接return
if (current == lockedBy) {
lockedCount++;
return;
}
// 线程初次获取锁
while (!lockedThread.compareAndSet(null, current)) {
PrintUtils.println(current + " 开始自旋");
ThreadUtils.sleep(1000);
}
lockedCount++;
}
public void unlock() {
Thread current = Thread.currentThread();
Thread lockedBy = lockedThread.get();
if (current != lockedBy) {
return;
}
// 如果请求释放的锁和之前获取线程的锁是同一个线程
lockedCount--;
if (lockedCount == 0) {
PrintUtils.println(current.getName() + " 工作完毕");
lockedThread.compareAndSet(current, null);
}
}
}
到此为止,博文基本结束,后面会继续对Java自己提供的可重入锁的源码进行解析,敬请期待。 本篇博文代码传送门