面试前必须要知道的【可重入锁 自旋锁】

在多线程编程中,锁是常用地控制并发的机制,对于临界区的资源,需要保证线程之间互斥地访问。

1. 可重入锁

可重入锁,也叫做递归锁,指的是多次对同一个锁进行加锁操作,都不会阻塞线程。实现思路:记录当前锁正在被哪个线程使用,采用计数来统计lock和unlock的调用次数。正常情况下,lock和unlock的调用次数应该相等,如果不相等就会死锁。

public class Test implements Runnable {
	ReentrantLock lock = new ReentrantLock(); //定义一个可重入锁

	public void get() {
		lock.lock(); //第一次调用lock()
		System.out.println(Thread.currentThread().getId());
		set();
		lock.unlock();
	}

	public void set() {
		lock.lock(); //第二次调用lock(),而且会成功,说明lock是可重入锁
		System.out.println(Thread.currentThread().getId());
		lock.unlock();
	}

	@Override
	public void run() {
		get();
	}

	public static void main(String[] args) {
		Test ss = new Test();
		new Thread(ss).start();
		new Thread(ss).start();
		new Thread(ss).start();
	}
}

2. 自旋锁

首先,看看初级的自旋锁实现方式:

public class SpinLock {
	private AtomicReference<Thread> owner =new AtomicReference<>();
	public void lock(){
		Thread current = Thread.currentThread();
		while(!owner.compareAndSet(null, current)){
		}
	}
	
	public void unlock (){
		Thread current = Thread.currentThread();
		owner.compareAndSet(current, null);
	}
}

实现思路:通过CAS(CompareAndSet)原子操作来更新变量。如果CAS返回true,表示获得了锁;否则,需要通过while循环检查,直到获得锁为止,这也是为什么叫做自旋锁的原因,需要不停的尝试获取锁。

2.1 初级版本的问题
  1. 同一线程前后两次调用lock(),会导致第二次调用lock时进行自旋,产生了死锁(因为第一次调用lock()之后,还没有unlock),说明这个锁不是可重入的。
  2. 如果问题一已经解决,当第一次调用unlock()时,就已经将锁释放了。实际上不应释放锁。
2.2 解决方案
  1. 针对问题一:在lock函数内,应验证线程是否为已经获得锁的线程
  2. 针对问题二:采用计数进行统计
public class SpinLock {
	private AtomicReference<Thread> owner =new AtomicReference<>();
	private int count =0;
	public void lock(){
		Thread current = Thread.currentThread();
		if(current==owner.get()) {
			count++;
			return ;
		}

		while(!owner.compareAndSet(null, current)){
		}
	}
	
	public void unlock (){
		Thread current = Thread.currentThread();
		if(current==owner.get()){
			if(count!=0){
				count--;
			}else{
				owner.compareAndSet(current, null);
			}
		}
	}
}

3. 参考资料

  1. Java锁的种类以及辨析(四):可重入锁
  2. 面试必问的CAS,你懂了吗?
posted @ 2020-02-26 19:51  wengle  阅读(890)  评论(3编辑  收藏  举报