Java线程拾遗(可重入锁)

对于锁的可重入锁来说,意味着某线程获取锁之后,该线程就可以进入任何一个该锁所同步着的代码块!!!
先举一个反面例子,非可重入锁!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<Thread> lockedThread = new AtomicReference<Thread>();
	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自己提供的可重入锁的源码进行解析,敬请期待。
本篇博文代码传送门

发布了257 篇原创文章 · 获赞 484 · 访问量 144万+

猜你喜欢

转载自blog.csdn.net/chunqiuwei/article/details/102814926