Java锁与公平锁浅析

简单锁的使用示例


lock.lock();
.....  ///do something

lock.unlock();
....

通过lock.lock() 进行资源竞争,竞争失败的进程被阻塞在lock()调用上,成功获得锁的进程将进入临界区,并在退出临界区时释放锁,然后其他进程再次进行竞争,并使得一个进程可以进入临界区。

如下是锁的一个简单demo

public class UnFairLock {
	private volatile boolean isLocked = false;
	private Thread lockedForThread = null;
	
	public synchronized void lock() throws InterruptedException{
		while(isLocked){
			wait();
		}
		lockedForThread = Thread.currentThread();
		isLocked = true;
	}
	
	public synchronized void unlock(){
		if(lockedForThread != Thread.currentThread()){
			throw new IllegalMonitorStateException("Current thread does't hold the lock");
		}
		
		isLocked = false;
		lockedForThread = null;
		this.notifyAll();
	}
	
}
该锁由一个boolean变量的标志符控制当前是否已经加锁, volatile修饰是必须的,使得每次读取标志符都直接从内存中获取,而不会因为缓存导致无法读取到最新变化。在lock中,循环等待也是必须的,尽管wait()方法会一直等待下去,没有唤醒不会自己活过来,但是,不能保证唤醒它的就是它需要等待的条件得到了满足(比如notifAll唤醒)。

另外一个变量LockedForThread,记录当前获得锁的进程。因为锁在解锁的时候需要判断解锁进程是否是获得锁的进程(java doc中有说明)。

但是这个锁在如下程序中会出现死锁,因为它不支持重入。

package cn.yuanye.concurrence.lock;

public class CriticalObject{
	
	private UnFairLock lock = new UnFairLock();
	
	public void f1() throws InterruptedException{
		lock.lock();
		System.out.println("get lock in f1(),try to invoke f2()");
		f2();
		lock.unlock();
	}
	
	public void f2() throws InterruptedException{
		lock.lock();
		System.out.println("get lock in f2()");
		lock.unlock();
	}
	
	public static void main(String[] args) throws InterruptedException{
		CriticalObject obj = new CriticalObject();
		obj.f1();
	}
}
此段程序会输入“get lock in f1(),try to invoke f2()”之后便停滞不前了,也就是阻塞在了f2()的调用上了。

重入是指,一个线程可以多次获得它已经获得的锁。在上例中,直接在主线程中调用了f1(),主线程获取到了锁权限,当调用f2()时,如果支持重入,那么它也应该能够重新获取到该锁的权限,而不是卡死在第二次加锁上。

如下是一个简单的支持重入的锁,与不支持重入锁相比,多了一个nlock变量,用于记录被加锁的次数。

package cn.yuanye.concurrence.lock;

public class ReentrancyLock {
	private volatile boolean isLocked = false;
	private int nlock = 0;                        //locked times
	private Thread lockedForThread = null;
	
	
	public synchronized void lock() throws InterruptedException{
		if(lockedForThread == Thread.currentThread()){   //invoke by the thread which owns the lock
			nlock ++ ;
			return;
		}
		
		while(isLocked){
			wait();
		}
		
		isLocked = true;
		nlock++;
		lockedForThread = Thread.currentThread();
	}
	
	public synchronized void unlock(){
		if(lockedForThread != Thread.currentThread()){
			throw new IllegalMonitorStateException(
					"Current thread does't hold the lock");
		}
		
		nlock --;
		
		if(nlock == 0){   
			isLocked = false;
			lockedForThread = null;
			notifyAll();
		}
		
	}
	
}

但是上面的锁机制都不是公平的。所谓公平,就是先提出锁请求的,先得到锁。但是上述的锁,却无法决定下一次应该由谁获得锁。notifyAll()会唤醒所有等待该锁的进程,notify() 会随机唤醒一个进程,所以都是不能满足要求的。

如下是一个公平锁的实现

package cn.yuanye.concurrence.lock;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


class LockObject{
	private volatile boolean isNotified = false;
	
	/**
	 * wait until the {@value isNotified} is true
	 * @throws InterruptedException 
	 * */
	public synchronized void doWait() throws InterruptedException{
		while(!isNotified){
			wait();
		}
		
		isNotified = false;
	}
	
	/**
	 * notify thread blocked in the doWait
	 * */
	public synchronized void doNotify(){
		isNotified = true;
		notify();
	}
	
	@Override
	public boolean equals(Object o){
		return (o == this);
	}
}


public class FairLock {
	private volatile boolean isLocked = false;
	private Thread lockedThread = null;
	private List<LockObject> locks = new LinkedList<LockObject>();
	
	public void lock() throws InterruptedException {
		LockObject lock = new LockObject();
		boolean isAvaliable = false;

		synchronized (this) {
			locks.add(lock);
		}

		while (!isAvaliable) {
			synchronized (this) {
				isAvaliable = !isLocked && locks.get(0) == lock;

				if (isAvaliable) {
					isLocked = true;
					locks.remove(0);
					lockedThread = Thread.currentThread();
					return;
				}
			}
			try {
				lock.doWait();
			} catch (InterruptedException e) {
				synchronized (this) {
					locks.remove(lock);
				}
				throw e;
			}
		}

	}

	public synchronized void unlock(){
		if(Thread.currentThread() != lockedThread){
			throw new IllegalMonitorStateException(
					"Calling thread has not locked this lock");
		}
		lockedThread = null;
		isLocked = false;
		
		if(locks.size() > 0){
			locks.get(0).doNotify();
		}
	}
}

要实现公平锁,就需要记录下各个线程申请所得顺序,在释放锁的时候根据该顺序进行通知。上例通过LockObject与各个申请锁的线程对应,并将这些锁对象顺的存入List,在释放锁的时候,顺序冲List获取对象,通知该对象对应的线程。

与UnFailLock和ReentrancyLock对比,FairLock的lock()没有synchronized修饰,而是在内部分两步进行了同步。

第一步,将对应的锁对象放入List末尾。

第二部,判断是否能够获取锁对象。判断依据是当前锁没有加锁并且该线程对应的锁对象在List的头部。


是否能加两步直接省去,而直接将lock()修饰为synchronized呢?不能!

在UnFailLock和ReentrancyLock中,可以这么做是因为wait方法会释放锁。而在FairLock中,lock.doWait() 是在锁对象上调用的wait方法,而不是在FairLock对象上,所以该方法不会释放在FairLock上的锁,注意lock.doWait() 是在同步块之外的。

 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值