并发基础_8_并发_锁_重入锁

重入锁


重入锁ReentrantLock,是基于AbstractQueuedSynchronizer的,建议最好先将上一篇文章读一下..


ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁

此外还支持获取锁时的公平性和非公平性选择


之前在写AbstractQueuedSynchronizer(队列同步器)的时候,写过一个独占锁的Demo Mutex

当一个线程调用Mutex的lock()方法获取锁之后,再次调用lock()方法,该线程将会被自己所阻塞。

简单的说,Mutex是一个不支持重进入的锁。


获取锁的公平性问题:

如果在绝对时间上,先对锁进行获取请求一定先被满足,那么这个锁是公平的,反之是不公平的

公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说获取锁是顺序的


事实上,公平锁的机制往往没有非公平的效率高,但并不是任何场景都以TPS(吞吐量)作为唯一指标;

公平锁能够减少"饥饿"发生的概率,等待越久的请求越是能够得到优先满足。


构造公平锁与非公平锁的源码:

默认构造非公平锁 
public ReentrantLock() {
	sync = new NonfairSync();
}

指定构造参数,公平锁
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}



synchronized 和 ReentrantLock

synchronized(锁)关键字是隐式的支持重进入,比如一个synchroinzed修饰的递归方法,在方法执行时,执行线程在获取了

锁之后仍能连续多次的获得该锁,不会出现获取了锁,在下一次获取锁时出现阻塞自己的情况。

ReentrantLock虽然没有像synchronized关键字一样支持隐式的重进入,但在调用lock()方法时,以及获取到锁的线程,

能够再次调用lock()方法获取锁而不被最



实现重进入

重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题:


a. 线程的再次获取

锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。


b. 锁的最终释放

线程重复N次获取锁,随后在第n次释放该锁后,其他线程能够获取到该锁。

锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,

而锁被释放时,计数自减,当计数为0时,表示锁已成功释放


在ReentrantLock源码中有一个静态内部类Sync,这个类继承了AQS,我们来看下:

abstract static class Sync extends AbstractQueuedSynchronizer {
	.............
}


Reentrant根据构造函数传入的参数,以此判断实例化公平锁还是非公平锁

我们下面根据非公平锁(默认)来看看获取锁的源码:

// 非公平锁类对象 继承了Sync实现了tryAcquire()方法,Sync继承了AQS 
static final class NonfairSync extends Sync {
	private static final long serialVersionUID = 7316153563782823691L;

	/**
	* Performs lock. Try immediate barge, backing up to normal acquire on
	* failure.
	*/
	final void lock() {
		if (compareAndSetState(0, 1))
				setExclusiveOwnerThread(Thread.currentThread());
		else
				acquire(1);
	}

	protected final boolean tryAcquire(int acquires) {
		return nonfairTryAcquire(acquires);
	}
	}


	// 该方法是在sync中的,非公平获取同步状态(锁)
	final boolean nonfairTryAcquire(int acquires) {

	// 获取当前线程
	final Thread current = Thread.currentThread();

	// 获取AQS中同步状态,state是volatile修饰的
	int c = getState();

	// 同步状态为0,允许获取锁
	if (c == 0) {
		
		// 获得该锁,并设置锁的状态
		if (compareAndSetState(0, acquires)) {
				
				// 设置锁的拥有者为当前线程
				setExclusiveOwnerThread(current);
				
				return true;
		}

	// 同步状态不为0,说明该锁已经被获取
	// 于是判断该锁的拥有者是不是当前线程,如果是设置state,如果不是,直接返回false
	} else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0) // overflow
				throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

从上面可以看出,已经获取过锁的线程,再次获取锁,只是增加state状态值的数量

这样子的话,在锁释放的时候,也得将累加的锁,全部释放掉。


非公平锁释放锁的源码:

// ReentarntLock的方法 
public void unlock() {
	sync.release(1);
	}

// AQS的方法
public final boolean release(int arg) {
	if (tryRelease(arg)) {
			Node h = head;
			if (h != null && h.waitStatus != 0)
					unparkSuccessor(h);
			return true;
	}
	return false;
}

//Sync中的方法
protected final boolean tryRelease(int releases) {
	
	// 获取状态,并且减去release参数(1)
	int c = getState() - releases;
	
	// 当前线程与持有锁的线程进行比较,如果不是一个线程,抛出异常
	if (Thread.currentThread() != getExclusiveOwnerThread())
			throw new IllegalMonitorStateException();
	
	// 是一个线程话,继续
	// 修改标示符参数
	boolean free = false;
	
	// 同步状态如果为0,说明锁已经成功释放
	if (c == 0) {
			free = true;
			setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}
如果该锁被同一线程获取了n次,那么前(n-1)次的tryRelease()方法必须返回false,只有当同步状态全部释放掉,才能返回true。

可以看到,代码中将同步状态=0,作为最终释放锁的条件,当同步状态为0,将占有线程设置为null,并返回true。



公平获取锁 与 非公平获取锁的区别

公平性是针对获取锁而言的;

如果一个锁是公平的,那么获取锁的顺序就应该符合请求的绝对时间顺序,也就是FIFO,就是先进先出


上面以非公平锁获取锁的源码中,我们发现,只要CAS设置同步状态成功,则表示当前线程获取了锁;

我们来看看公平锁是怎么做的:

/**
* Sync object for fair locks<br>
* (公平锁)
*/
static final class FairSync extends Sync {
	private static final long serialVersionUID = -3000897897090466540L;

	final void lock() {
			acquire(1);
	}

	// 独占模式下,获取锁
	protected final boolean tryAcquire(int acquires) {

	// 当前线程
	final Thread current = Thread.currentThread();

	// 获取锁的状态
	int c = getState();

	// 当c==0,表示锁没有被任何线程占用
	if (c == 0) {

	// hasQueuedPredecessors:判断当前线程"是不是队列中第一个线程"
	// compareAndSetState:如果是,获取该锁的状态。
	if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {

	// setExclusiveOwnerThread:设置锁的拥有者为当前线程
	setExclusiveOwnerThread(current);
	return true;
	}
	}
	// 如果c != 0,表示该锁已经被线程占用,则判断该锁是否是当前线程所占有,是设置state,否则直接返回false。
	else if (current == getExclusiveOwnerThread()) {
			int nextc = c + acquires;
			if (nextc < 0)
					throw new Error("Maximum lock count exceeded");
			setState(nextc);
			return true;
	}
	return false;
	}
}
是不是感觉和非公平获取锁没有什么区别??

仔细看会发现,在state状态等于0之后,做的那个判断,在非公平状态下,直接通过CAS设置,以获取同步状态。


这里公平状态下会调用hasQueuedPredecessors()方法,这个方法是干嘛的呢? 

这个方法判断当前节点是否有前驱节点,如果有前驱节点,那说明,有其他线程比当前线程更早的请求了锁,因此需要等待前驱节点获取并释放锁之后,才能继续获取锁。


原书中对公平锁和非公平锁进行了性能测试,我这里就不做重复工作了..


直说结论:

公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。

非公平锁虽然可能造成线程饥饿,但是极少进行线程切换,保证了更大的吞吐量。


参考资料:
http://www.cnblogs.com/xrq730/p/4979021.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值