并发——读写锁初探

简介: 适用场景读操作频发,写操作不频繁。两个线程同时读取同一个共享资源没有任何问题如果一个线程对共享资源进行写操作,此时就不能有其他线程对共享资源进行读写条件分析写操作的优先级高于读操作,在读操作频繁的场景下,如...

适用场景

  • 读操作频发,写操作不频繁。
  • 两个线程同时读取同一个共享资源没有任何问题
  • 如果一个线程对共享资源进行写操作,此时就不能有其他线程对共享资源进行读写

条件分析

  • 写操作的优先级高于读操作,在读操作频繁的场景下,如果写操作没有高于读操作的优先级,就会导致写操作线程“饿死”的情况发生
  • 读操作触发条件:

    1. 没有线程正在执行写操作
    2. 没有线程在等待执行写操作
  • 写操作触发条件:没有线程正在执行读写操作

代码实现

public class ReadWriteLock {

  private int readers = 0;
  private int writers = 0;
  private int writeRequests = 0;

  public synchronized void lockRead() throws InterruptedException {

    while (writers > 0 || writeRequests > 0) {
      wait();
    }
    readers++;
  }

  public synchronized void unlockRead() {
    readers--;
    notifyAll();
  }

  public synchronized void lockWrite() throws InterruptedException {

    writeRequests++;
    while (readers > 0 || writers > 0) {
      wait();
    }

    writeRequests--;
    writers++;
  }

  public synchronized void unlockWrite() throws InterruptedException {

    writers--;
    notifyAll();
  }
}
ReadWriteLockl类中通过读锁、写锁以两个锁的状态控制线程的读、写操作:
writers表示当前正在使用写锁的线程数量; 
writeRequests表示等待请求写锁的线程数量;
readers表示请求读锁的线程数量;
说明:
1.线程在获取读锁的时候,只要没有线程拥有写锁即writers==0同时没有线程请求写锁即writerRquests==0,那么线程就能成功获取读锁;
2.当一个线程想获取写锁的时候,会把写锁的请求数加1即writeRequests++,然后再尝试获取能否获取写锁,如果当前没有线程占用写锁即writers==0,那么此时就能成功获取写锁,同时writers++;如果wirters>0表示写锁此时被其他线程占用那么当前线程会被阻塞等待写锁释放时被唤醒。
3.写操作的优先级高于读操作的优先级体现在,线程请求读锁时会判断持有写锁的线程数和请求写锁的线程数,即while(writers > 0 || writeRequests > 0){wait();},而线程请求写锁时只需要判断持有写锁和读锁的线程数即可,即while(readers > 0 || writers > 0) {wait();}

锁重入

锁重入,是指同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。ReentrantLock 和synchronized 都是可重入锁,可重入锁最大的作用是避免死锁。
以自旋锁为例,如果自旋锁不是可重入锁的话,如果一个线程在第一次获取锁执行同步代码前提下,第二次再执行同步代码就产生了死锁。
以前面的代码为例:

  1. 此时有两个线程Thread1,Thread2
  2. Thread2在Thread1获取读锁以后请求写锁,readers=1、writers=0、writeRequests=1
  3. 若此时Thread1再次尝试获取同一个读锁,根据已有的代码writers > 0 || writeRequests > 0,因为Thread请求写锁的原因导致该条件成立,Thread1进入阻塞状态,死锁出现

读锁重入

public class ReadWriteLock{

 private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
 private int writers = 0;
 private int writeRequests = 0;

 public synchronized void lockRead() throws InterruptedException{
   Thread callingThread = Thread.currentThread();
   while(! canGrantReadAccess(callingThread)){
     wait();
   }
   readingThreads.put(callingThread, (getAccessCount(callingThread) + 1));
 }

 public synchronized void unlockRead(){
   Thread callingThread = Thread.currentThread();
   int accessCount = getAccessCount(callingThread);
   if(accessCount == 1) {
    readingThreads.remove(callingThread);
   } else {
    readingThreads.put(callingThread, (accessCount -1));
   }
   notifyAll();
 }

 private boolean canGrantReadAccess(Thread callingThread){
   if(writers > 0) return false;
   if(isReader(callingThread) return true;
   if(writeRequests > 0) return false;
   return true;
 }

 private int getReadAccessCount(Thread callingThread){
   Integer accessCount = readingThreads.get(callingThread);
   if(accessCount == null) return 0;
   return accessCount.intValue();
}

 private boolean isReader(Thread callingThread){
   return readingThreads.get(callingThread) != null;
 }
}
读锁的可重入有两种情况:
1.当前程序中没有线程请求写锁(这种情况是几乎不存在)
2.当前程序中有线程请求写锁也有线程请求读锁,并且有线程已经得到了读锁

第二种情况是最常见的,因此我们需要知道哪些线程是持有读锁的

因此在代码中使用Map来存储已经持有读锁的线程和对应线程获取读锁的次数,通过Map就可以判断对应的线程是否持有读锁,调整之后的代码在原有判断"writeRequests >0"和"writers > 0"还加上了判断当前线程是否持有读锁的判断逻辑

写锁重入

public class ReadWriteLock{
 private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
 private int writeAccesses = 0;
 private int writeRequests = 0;
 private Thread writingThread = null;

 public synchronized void lockWrite() throws InterruptedException{

   writeRequests++;
   Thread callingThread = Thread.currentThread();
   while(!canGrantWriteAccess(callingThread)){
    wait();
   }
   writeRequests--;
   writeAccesses++;
   writingThread = callingThread;
 }

 public synchronized void unlockWrite() throws InterruptedException{
   writeAccesses--;
   if(writeAccesses == 0){
     writingThread = null;
   }
   notifyAll();
 }

 private boolean canGrantWriteAccess(Thread callingThread){
   if(hasReaders()) return false;
   if(writingThread == null) return true;
   if(!isWriter(callingThread)) return false;
   return true;
 }

 private boolean hasReaders(){
   return readingThreads.size() > 0;
 }

 private boolean isWriter(Thread callingThread){
   return writingThread == callingThread;
 }
}
写锁重入,是在当前程序里有且只有一个线程持有写锁,如果写锁重入,说明当前程序中没有线程持有读锁,写锁重入只有持有写锁的线程才能重入,其他的线程就需要进入阻塞状态
目录
相关文章
|
6天前
|
弹性计算 人工智能 安全
云上十五年——「弹性计算十五周年」系列客户故事(第二期)
阿里云弹性计算十五年深耕,以第九代ECS g9i实例引领算力革新。携手海尔三翼鸟、小鹏汽车、微帧科技等企业,实现性能跃升与成本优化,赋能AI、物联网、智能驾驶等前沿场景,共绘云端增长新图景。
|
12天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
|
3天前
|
云安全 人工智能 安全
Dify平台集成阿里云AI安全护栏,构建AI Runtime安全防线
阿里云 AI 安全护栏加入Dify平台,打造可信赖的 AI
|
11天前
|
人工智能 自然语言处理 自动驾驶
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
|
6天前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
570 17
|
5天前
|
人工智能 Java Nacos
基于 Spring AI Alibaba + Nacos 的分布式 Multi-Agent 构建指南
本文将针对 Spring AI Alibaba + Nacos 的分布式多智能体构建方案展开介绍,同时结合 Demo 说明快速开发方法与实际效果。
393 34
|
11天前
|
编解码 自然语言处理 文字识别
Qwen3-VL再添丁!4B/8B Dense模型开源,更轻量,仍强大
凌晨,Qwen3-VL系列再添新成员——Dense架构的Qwen3-VL-8B、Qwen3-VL-4B 模型,本地部署友好,并完整保留了Qwen3-VL的全部表现,评测指标表现优秀。
713 7
Qwen3-VL再添丁!4B/8B Dense模型开源,更轻量,仍强大