• Java锁与公平锁浅析


    简单锁的使用示例

     

     

    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() 是在同步块之外的。

  • 相关阅读:
    剑指offer(45)扑克牌顺子
    剑指offer(44)单词翻转序列
    剑指offer(43)左旋转字符串
    剑指offer(42)和为S的字符串
    剑指offer(41)和为S的连续正数序列
    剑指offer(40)数组中只出现一次的数字
    剑指offer(39)平衡二叉树
    面试金典——字符串压缩
    LeetCode——恢复二叉搜索树
    LeetCode——修剪二叉搜索树
  • 原文地址:https://www.cnblogs.com/haichun/p/3512366.html
Copyright © 2020-2023  润新知