聊聊高并发(十五)实现一个简单的读-写锁(共享-排他锁)

读写锁是数据库中很常见的锁,又叫共享-排他锁,S锁和X锁。读写锁在大量读少量写的情况下有很高的效率优势。

读写锁是基于普通的互斥锁构建出来的更复杂的锁,它有两个基本特点:

1. 当任一线程持有读锁或写锁时,不允许其他线程再持有写锁

2. 当任一线程持有写锁时,不允许其他线程再持有读锁


也就是说,写锁是排他的,只要有一个线程持有写锁,就不允许其他线程再上锁。读锁是共享的,可以有多个线程持有读锁,但不允许同时持有写锁。


读锁和写锁还存在一个锁升级的问题,比如一个线程先持有了读锁,想升级成写锁,这时候基本的读写锁不能支持锁升级,有可能造成死锁,需要更新锁的操作,后面会细说更新锁。这篇只涉及到如何实现一个基本的读-写锁。


首先是一个非公平的读-写锁实现。

1. 使用一个volatile boolean write标志位来表示是否有线程持有写锁

2. 使用一个volatile 计数器来记录持有读锁的个数

3. 使用两个条件来表示条件谓词: existReadCondition, existWriteCondition

4. 需要一个内部锁来进行线程的同步,这里使用非公平的显式锁ReentrantLock

5. 当有线程持有写锁时,设置write = true,让其他线程在existWriteCondition上等待

6. 当有线程持有读锁时,让需要获得写锁的线程在existReadCondition等待,需要读锁的线程可以获得读锁,并设置读锁计数器加1

7. 释放写锁时,需要唤醒在existWriteCondition上等待的线程

8. 释放读锁时,设置读锁计数器减1,当读锁计数器等于0时表示当前没有读锁存在,唤醒在existReadCondition条件等待的线程

9. 值得注意的是这个简单的读-写锁实现,在同一时刻,不管是读锁还是写锁都需要竞争内部锁,也就是说即使是多个读锁并发,同一时刻也只有一个线程能拿到内部锁,只是这个锁的持有时间很短,只设置一个计数器就释放了。


读写锁的接口如下:

[java] view plain copy
  1. package com.zc.lock;  
  2.   
  3.   
  4. public interface ReadWriteLock {  
  5.       
  6.     public Lock readLock();  
  7.       
  8.     public Lock writeLock();  
  9.       
  10. }  

非公平的读-写锁实现

[java] view plain copy
  1. package com.zc.lock;  
  2.   
  3. import java.util.concurrent.locks.ReentrantLock;  
  4.   
  5. /** 
  6.  * 读写锁的特点有两个: 
  7.  * 1. 当读锁和写锁上锁时,不允许有写者锁上锁 
  8.  * 2. 当写锁上锁时,不允许有读者锁和写者锁上锁 
  9.  * 也就是说写锁是排他锁,同时只能有一个写锁 
  10.  * 读锁是共享锁,可以有多个读锁存在 
  11.  * **/  
  12.   
  13. public class SimpleReadWriteLock implements ReadWriteLock{  
  14.   
  15.     private final java.util.concurrent.locks.Lock lock;  
  16.       
  17.     private final java.util.concurrent.locks.Condition existReadCondition;  
  18.       
  19.     private final java.util.concurrent.locks.Condition existWriteCondition;  
  20.       
  21.     private final Lock readLock;  
  22.       
  23.     private final Lock writeLock;  
  24.       
  25.     private volatile boolean write;  
  26.       
  27.     private volatile int readCount;  
  28.       
  29.     public SimpleReadWriteLock(){  
  30.         lock = new ReentrantLock();  
  31.         readLock = new ReadLock();  
  32.         writeLock = new WriteLock();  
  33.         existReadCondition = lock.newCondition();  
  34.         existWriteCondition = lock.newCondition();  
  35.         write = false;  
  36.         readCount = 0;  
  37.     }  
  38.       
  39.     private class ReadLock implements Lock{  
  40.   
  41.         @Override  
  42.         public void lock() {  
  43.             lock.lock();  
  44.             try{  
  45.                 while(write){  
  46.                     try {  
  47.                         existWriteCondition.await();  
  48.                     } catch (InterruptedException e) {  
  49.                         throw new RuntimeException("Interrupted");  
  50.                     }  
  51.                 }  
  52.                 readCount ++;  
  53.             }finally{  
  54.                 lock.unlock();  
  55.             }  
  56.         }  
  57.   
  58.         @Override  
  59.         public void unlock() {  
  60.             lock.lock();  
  61.             try{  
  62.                 readCount --;  
  63.                 if(readCount == 0){  
  64.                     existReadCondition.signalAll();  
  65.                 }  
  66.             }finally{  
  67.                 lock.unlock();  
  68.             }  
  69.         }  
  70.           
  71.     }  
  72.       
  73.     private class WriteLock implements Lock{  
  74.   
  75.         @Override  
  76.         public void lock() {  
  77.             lock.lock();  
  78.             try{  
  79.                 while(readCount > 0){  
  80.                     try {  
  81.                         existReadCondition.await();  
  82.                     } catch (InterruptedException e) {  
  83.                         throw new RuntimeException("Interrupted");  
  84.                     }  
  85.                 }  
  86.                 while(write){  
  87.                     try {  
  88.                         existWriteCondition.await();  
  89.                     } catch (InterruptedException e) {  
  90.                         throw new RuntimeException("Interrupted");  
  91.                     }  
  92.                 }  
  93.                 write = true;  
  94.             }finally{  
  95.                 lock.unlock();  
  96.             }  
  97.         }  
  98.   
  99.         @Override  
  100.         public void unlock() {  
  101.             lock.lock();  
  102.             try{  
  103.                 write = false;  
  104.                 existWriteCondition.signalAll();  
  105.             }finally{  
  106.                 lock.unlock();  
  107.             }  
  108.         }  
  109.           
  110.     }  
  111.       
  112.     @Override  
  113.     public Lock readLock() {  
  114.         return readLock;  
  115.     }  
  116.   
  117.     @Override  
  118.     public Lock writeLock() {  
  119.         return writeLock;  
  120.     }  
  121.   
  122. }  

非公平的读-写锁在大量读存在的情况下有饥饿的问题,写锁在readCount > 0这个条件谓词下等待,也就是说一旦有读锁存在,写操作就要等待,而读操作一般都是远高于写操作的,并且没有先来先服务的公平性,后来的读操作很可能快于写操作获得锁。写锁有可能长时间的等待锁而不能获取锁。


下面实现一个公平的读-写锁:

1. 使用公平的显式锁作为内部锁,保证先来先服务的公平性

2. 使用一个readAccquired和readReleased计数器来记录获取读锁和释放读锁的线程个数

3. 当readAccquired == readReleased时表示读锁已经全部释放,可以获得写锁

4. 最大的改进在这里,获取写锁时分为两步,第一步当没有写锁存在时,就设置write标志为true表示要获取写锁。当write为true时,后来的读锁就一直等待existWriteConditiont条件释放,而不能增加readAccquire计数器。当之前已经获得的读锁都释放后,写锁获得锁。只有等待写锁释放后,后续的读锁才能继续操作。

它的改进主要是在readAccquired == readRelease 条件谓词等待,而不是readCount > 0条件谓词等待。readAccquired的数量即肯定小于readCount,而且只要之前没有写锁存在,就优先让写锁获取。能够保证写锁快速获得锁


[java] view plain copy
  1. package com.zc.lock;  
  2.   
  3. import java.util.concurrent.locks.ReentrantLock;  
  4.   
  5. /** 
  6.  * 公平的读写锁,优先让写锁能更快地获得锁,写锁尝试获得锁时,读锁会进入等待,让写锁先获得锁 
  7.  *  
  8.  * **/  
  9. public class FairReadWriteLock implements ReadWriteLock{  
  10.   
  11.     private final java.util.concurrent.locks.Lock lock;  
  12.       
  13.     private final java.util.concurrent.locks.Condition existReadCondition;  
  14.       
  15.     private final java.util.concurrent.locks.Condition existWriteCondition;  
  16.       
  17.     private final Lock readLock;  
  18.       
  19.     private final Lock writeLock;  
  20.       
  21.     private volatile boolean write;  
  22.       
  23.     private volatile int readAccquired;  
  24.       
  25.     private volatile int readReleased;  
  26.       
  27.     public FairReadWriteLock(){  
  28.         lock = new ReentrantLock(true);  
  29.         existReadCondition = lock.newCondition();  
  30.         existWriteCondition = lock.newCondition();  
  31.         readLock = new ReadLock();  
  32.         writeLock = new WriteLock();  
  33.         write = false;  
  34.         readAccquired = 0;  
  35.         readReleased = 0;  
  36.     }  
  37.       
  38.       
  39.     private class ReadLock implements Lock{  
  40.   
  41.         @Override  
  42.         public void lock() {  
  43.             lock.lock();  
  44.             try{  
  45.                 while(write){  
  46.                     try {  
  47.                         existWriteCondition.await();  
  48.                     } catch (InterruptedException e) {  
  49.                         throw new RuntimeException("Interrupted");  
  50.                     }  
  51.                 }  
  52.                 readAccquired ++;  
  53.             }finally{  
  54.                 lock.unlock();  
  55.             }  
  56.         }  
  57.   
  58.         @Override  
  59.         public void unlock() {  
  60.             lock.lock();  
  61.             try{  
  62.                 readReleased ++;  
  63.                 if(readReleased == readAccquired){  
  64.                     existReadCondition.signalAll();  
  65.                 }  
  66.             }finally{  
  67.                 lock.unlock();  
  68.             }  
  69.         }  
  70.           
  71.     }  
  72.       
  73.     private class WriteLock implements Lock{  
  74.   
  75.         @Override  
  76.         public void lock() {  
  77.             lock.lock();  
  78.             try{  
  79.                 while(write){  
  80.                     try {  
  81.                         existWriteCondition.await();  
  82.                     } catch (InterruptedException e) {  
  83.                         throw new RuntimeException("Interrupted");  
  84.                     }  
  85.                 }  
  86.                 // 让新加入的读锁不能增加readAccquired  
  87.                 write = true;  
  88.                 while(readAccquired != readAccquired){  
  89.                     try {  
  90.                         existReadCondition.await();  
  91.                     } catch (InterruptedException e) {  
  92.                         throw new RuntimeException("Interrupted");  
  93.                     }  
  94.                 }  
  95.             }finally{  
  96.                 lock.unlock();  
  97.             }  
  98.         }  
  99.   
  100.         @Override  
  101.         public void unlock() {  
  102.             lock.lock();  
  103.             try{  
  104.                 write = false;  
  105.                 existWriteCondition.signalAll();  
  106.             }finally{  
  107.                 lock.unlock();  
  108.             }  
  109.         }  
  110.           
  111.     }  
  112.       
  113.     @Override  
  114.     public Lock readLock() {  
  115.         return readLock;  
  116.     }  
  117.   
  118.     @Override  
  119.     public Lock writeLock() {  
  120.         return writeLock;  
  121.     }  
  122.   
  123. }  

没有更多推荐了,返回首页