简单锁的使用示例
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() 是在同步块之外的。