简单锁的使用示例

 

 

  1. lock.lock();  
  2. .....  ///do something  
  3.   
  4. lock.unlock();  
  5. ....  


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

 

如下是锁的一个简单demo

 

  1. public class UnFairLock {  
  2.     private volatile boolean isLocked = false;  
  3.     private Thread lockedForThread = null;  
  4.       
  5.     public synchronized void lock() throws InterruptedException{  
  6.         while(isLocked){  
  7.             wait();  
  8.         }  
  9.         lockedForThread = Thread.currentThread();  
  10.         isLocked = true;  
  11.     }  
  12.       
  13.     public synchronized void unlock(){  
  14.         if(lockedForThread != Thread.currentThread()){  
  15.             throw new IllegalMonitorStateException("Current thread does't hold the lock");  
  16.         }  
  17.           
  18.         isLocked = false;  
  19.         lockedForThread = null;  
  20.         this.notifyAll();  
  21.     }  
  22.       
  23. }  

该锁由一个boolean变量的标志符控制当前是否已经加锁,volatile修饰时必须的,使得每次读取标志符都直接从内存中获取,而不会因为缓存导致无法读取到最新变化。在lock中,循环等待也是必须的,尽管wait()方法会一直等待下去,没有唤醒不会自己活过来,但是,不能保证唤醒它的就是它需要等待的条件得到了满足。

 

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

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

 

  1. package cn.yuanye.concurrence.lock;  
  2.   
  3. public class CriticalObject{  
  4.       
  5.     private UnFairLock lock = new UnFairLock();  
  6.       
  7.     public void f1() throws InterruptedException{  
  8.         lock.lock();  
  9.         System.out.println("get lock in f1(),try to invoke f2()");  
  10.         f2();  
  11.         lock.unlock();  
  12.     }  
  13.       
  14.     public void f2() throws InterruptedException{  
  15.         lock.lock();  
  16.         System.out.println("get lock in f2()");  
  17.         lock.unlock();  
  18.     }  
  19.       
  20.     public static void main(String[] args) throws InterruptedException{  
  21.         CriticalObject obj = new CriticalObject();  
  22.         obj.f1();  
  23.     }  
  24. }  

此段程序会输入“get lock in f1(),try to invoke f2()”之后便停滞不前了,也就是阻塞在了f2()的调用上了。

 

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

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

  1. package cn.yuanye.concurrence.lock;  
  2.   
  3. public class ReentrancyLock {  
  4.     private volatile boolean isLocked = false;  
  5.     private int nlock = 0;                        //locked times  
  6.     private Thread lockedForThread = null;  
  7.       
  8.       
  9.     public synchronized void lock() throws InterruptedException{  
  10.         if(lockedForThread == Thread.currentThread()){   //invoke by the thread which owns the lock  
  11.             nlock ++ ;  
  12.             return;  
  13.         }  
  14.           
  15.         while(isLocked){  
  16.             wait();  
  17.         }  
  18.           
  19.         isLocked = true;  
  20.         nlock++;  
  21.         lockedForThread = Thread.currentThread();  
  22.     }  
  23.       
  24.     public synchronized void unlock(){  
  25.         if(lockedForThread != Thread.currentThread()){  
  26.             throw new IllegalMonitorStateException(  
  27.                     "Current thread does't hold the lock");  
  28.         }  
  29.           
  30.         nlock --;  
  31.           
  32.         if(nlock == 0){     
  33.             isLocked = false;  
  34.             lockedForThread = null;  
  35.             notifyAll();  
  36.         }  
  37.           
  38.     }  
  39.       
  40. }  

 

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

如下是一个公平锁的实现

 

  1. package cn.yuanye.concurrence.lock;  
  2.   
  3. import java.util.LinkedList;  
  4. import java.util.List;  
  5. import java.util.concurrent.CountDownLatch;  
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8.   
  9. class LockObject{  
  10.     private volatile boolean isNotified = false;  
  11.       
  12.     /** 
  13.      * wait until the {@value isNotified} is true 
  14.      * @throws InterruptedException  
  15.      * */  
  16.     public synchronized void doWait() throws InterruptedException{  
  17.         while(!isNotified){  
  18.             wait();  
  19.         }  
  20.           
  21.         isNotified = false;  
  22.     }  
  23.       
  24.     /** 
  25.      * notify thread blocked in the doWait 
  26.      * */  
  27.     public synchronized void doNotify(){  
  28.         isNotified = true;  
  29.         notify();  
  30.     }  
  31.       
  32.     @Override  
  33.     public boolean equals(Object o){  
  34.         return (o == this);  
  35.     }  
  36. }  
  37.   
  38.   
  39. public class FairLock {  
  40.     private volatile boolean isLocked = false;  
  41.     private Thread lockedThread = null;  
  42.     private List<LockObject> locks = new LinkedList<LockObject>();  
  43.       
  44.     public void lock() throws InterruptedException {  
  45.         LockObject lock = new LockObject();  
  46.         boolean isAvaliable = false;  
  47.   
  48.         synchronized (this) {  
  49.             locks.add(lock);  
  50.         }  
  51.   
  52.         while (!isAvaliable) {  
  53.             synchronized (this) {  
  54.                 isAvaliable = !isLocked && locks.get(0) == lock;  
  55.   
  56.                 if (isAvaliable) {  
  57.                     isLocked = true;  
  58.                     locks.remove(0);  
  59.                     lockedThread = Thread.currentThread();  
  60.                     return;  
  61.                 }  
  62.             }  
  63.             try {  
  64.                 lock.doWait();  
  65.             } catch (InterruptedException e) {  
  66.                 synchronized (this) {  
  67.                     locks.remove(lock);  
  68.                 }  
  69.                 throw e;  
  70.             }  
  71.         }  
  72.   
  73.     }  
  74.   
  75.     public synchronized void unlock(){  
  76.         if(Thread.currentThread() != lockedThread){  
  77.             throw new IllegalMonitorStateException(  
  78.                     "Calling thread has not locked this lock");  
  79.         }  
  80.         lockedThread = null;  
  81.         isLocked = false;  
  82.           
  83.         if(locks.size() > 0){  
  84.             locks.get(0).doNotify();  
  85.         }  
  86.     }  
  87. }  

 

 

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

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

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

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

 

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

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