千家信息网

怎样理解Java中的锁

发表于:2025-01-23 作者:千家信息网编辑
千家信息网最后更新 2025年01月23日,这篇文章将为大家详细讲解有关怎样理解Java中的锁,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。ReadWriteLock接口读写锁维护一对关联锁,一
千家信息网最后更新 2025年01月23日怎样理解Java中的锁

这篇文章将为大家详细讲解有关怎样理解Java中的锁,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

ReadWriteLock接口

读写锁维护一对关联锁,一个只用于读操作,一个只用于写操作。读锁可以由多个线程同时持有,又称共享锁。写锁同一时间只能由一个线程持有,又称互斥锁。同一时间,两把锁不能被不同线程持有。读写锁适合读取操作多于写入操作的场景,改进互斥锁的性能,比如集合的并发安全性改造,缓存组件等。

ReentrantReadWriteLock实现原理分析

  1. ReentrantReadWriteLock需要一个owner用来标记那个写操作的线程获取到了锁,owner只会标记写操作的线程引用,不会标记读操作的线程,一个writeCount用来记录写操作加锁的次数, 一个readCount用来记录读操作加锁的次数,还有一个waiters等待队列用来存放没有抢到锁的线程列表

  2. 当有写操作线程进来时,会先判断readCount的值,如果readCount为0说明读锁未被占用

  3. 然后判断writeCount的值,如果writeCount为0,说明写锁未被占用

  4. 然后通过CAS操作进行抢锁将writeCount值加1,如果抢到锁则将owner设置为当前写操作线程的引用

  5. 如果writeCount不为0同时owner指向当前写线程的引用,则将writeCount的值加1

  6. 如果writeCount不为0同时owner指向的不是当前写线程的引用,则将则将线程放入等待队列

  7. 如果CAS抢锁失败,则将线程放入等待队列

  8. 如果写操作线程进来时,readCount不为0说明读锁已被占用,则将线程放入等待队列

  9. 当有读操作线程进来时,会先判断writeCount的值,如果writeCount为0说明写锁未被占用

  10. 然后通过CAS将readCount的值加1

  11. 如果读操作线程进来时,writeCount不为0说明写锁被占用

  12. 如果写锁是被当前线程占用则该线程可以继续获得读锁,即锁降级

  13. 如果写锁不是被当前线程占用,则将线程放入等待队列

  14. 当有写线程释放锁时,会将writeCount的值减1,如果writeCount的值为0,则将owner设为null同时唤醒等待队列头部的线程出队列进行抢锁操作

  15. 如果等待队列的头部线程是读操作,则会进行CAS操作将readCount值加1同时唤醒下一个等待线程

  16. 如果下一个线程还是读操作,则会进行CAS操作将readCount值加1并且继续唤醒下一个等待线程

  17. 如果下一个线程是写操作,则不会唤醒需要等到将读锁释放完之后才会唤醒

手动实现ReentrantReadWriteLock示例:

public class MyReadWriteLock {  private AtomicInteger readCount = new AtomicInteger(0);  private AtomicInteger writeCount = new AtomicInteger(0);  // 独占锁 拥有者  private AtomicReference owner = new AtomicReference<>();  // 等待队列  private volatile LinkedBlockingQueue waiters = new LinkedBlockingQueue();  class WaitNode {    int type = 0; // 0 为想获取独占锁的线程,  1为想获取共享锁的线程    Thread thread = null;    int arg = 0;    public WaitNode(Thread thread, int type, int arg) {      this.thread = thread;      this.type = type;      this.arg = arg;    }  }  // 获取独占锁  public void lockWrite() {    int arg = 1;    // 尝试获取独占锁,若成功,退出方法,    若失败...    if (!tryLockWrite(arg)) {      // 标记为独占锁      WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);      waiters.offer(waitNode); // 进入等待队列      // 循环尝试拿锁      for (; ; ) {        // 若队列头部是当前线程        WaitNode head = waiters.peek();        if (head != null && head.thread == Thread.currentThread()) {          if (!tryLockWrite(arg)) { // 再次尝试获取 独占锁            LockSupport.park(); // 若失败,挂起线程          } else { // 若成功获取            waiters.poll(); //  将当前线程从队列头部移除            return; // 并退出方法          }        } else { // 若不是队列头部元素          LockSupport.park(); // 将当前线程挂起        }      }    }  }  // 释放独占锁  public boolean unlockWrite() {    int arg = 1;    // 尝试释放独占锁 若失败返回true,若失败...    if (tryUnlockWrite(arg)) {      WaitNode next = waiters.peek(); // 取出队列头部的元素      if (next != null) {        Thread th = next.thread;        LockSupport.unpark(th); // 唤醒队列头部的线程      }      return true; // 返回true    }    return false;  }  // 尝试获取独占锁  public boolean tryLockWrite(int acquires) {    // 如果read count !=0 返回false    if (readCount.get() != 0) return false;    int wct = writeCount.get(); // 拿到 独占锁 当前状态    if (wct == 0) {      if (writeCount.compareAndSet(wct, wct + acquires)) { // 通过修改state来抢锁        owner.set(Thread.currentThread()); //  抢到锁后,直接修改owner为当前线程        return true;      }    } else if (owner.get() == Thread.currentThread()) {      writeCount.set(wct + acquires); // 修改count值      return true;    }    return false;  }  // 尝试释放独占锁  public boolean tryUnlockWrite(int releases) {    // 若当前线程没有 持有独占锁    if (owner.get() != Thread.currentThread()) {      throw new IllegalMonitorStateException(); // 抛IllegalMonitorStateException    }    int wc = writeCount.get();    int nextc = wc - releases; // 计算 独占锁剩余占用    writeCount.set(nextc); // 不管是否完全释放,都更新count值    if (nextc == 0) { // 是否完全释放      owner.compareAndSet(Thread.currentThread(), null);      return true;    } else {      return false;    }  }  // 获取共享锁  public void lockRead() {    int arg = 1;    if (tryLockRead(arg) < 0) { // 如果tryAcquireShare失败      // 将当前进程放入队列      WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);      waiters.offer(node); // 加入队列      for (; ; ) {        // 若队列头部的元素是当前线程        WaitNode head = waiters.peek();        if (head != null && head.thread == Thread.currentThread()) {          if (tryLockRead(arg) >= 0) { // 尝试获取共享锁,  若成功            waiters.poll(); // 将当前线程从队列中移除            WaitNode next = waiters.peek();            if (next != null && next.type == 1) { // 如果下一个线程也是等待共享锁              LockSupport.unpark(next.thread); // 将其唤醒            }            return; // 退出方法          } else { // 若尝试失败            LockSupport.park(); // 挂起线程          }        } else { // 若不是头部元素          LockSupport.park();        }      }    }  }  // 解锁共享锁  public boolean unLockRead() {    int arg = 1;    if (tryUnLockRead(arg)) { // 当read count变为0,才叫release share成功      WaitNode next = waiters.peek();      if (next != null) {        LockSupport.unpark(next.thread);      }      return true;    }    return false;  }  // 尝试获取共享锁  public int tryLockRead(int acquires) {    for (; ; ) {      if (writeCount.get() != 0 && owner.get() != Thread.currentThread()) return -1;      int rct = readCount.get();      if (readCount.compareAndSet(rct, rct + acquires)) {        return 1;      }    }  }  // 尝试解锁共享锁  public boolean tryUnLockRead(int releases) {    for (; ; ) {      int rc = readCount.get();      int nextc = rc - releases;      if (readCount.compareAndSet(rc, nextc)) {        return nextc == 0;      }    }  }}

锁降级

锁降级指的是写锁降级为读锁,是指持有写锁的同时,再获取读锁,随后释放写锁的过程。 写锁是线程独占,读锁是线程共享,所以写锁降级为读锁可行,而读锁升级为写锁不可行。

代码示例:

class TeacherInfoCache {  static volatile boolean cacheValid;  static final ReadWriteLock rwl = new ReentrantReadWriteLock();  static Object get(String dataKey) {    Object data = null;    // 读取数据,加读锁    rwl.readLock().lock();    try {      if (cacheValid) {        data = Redis.data.get(dataKey);      } else {        // 通过加锁的方式去访问DB,加写锁        rwl.readLock().unlock();        rwl.writeLock().lock();        try {          if (!cacheValid) {            data = DataBase.queryUserInfo();            Redis.data.put(dataKey, data);            cacheValid = true;          }        } finally {          // 锁降级          rwl.readLock().lock();          rwl.writeLock().unlock();        }      }      return data;    } finally {      rwl.readLock().unlock();    }  }}class DataBase {  static String queryUserInfo() {    System.out.println("查询数据库。。。");    return "name:Kody,age:40,gender:true,";  }}class Redis {  static Map data = new HashMap<>();}

关于怎样理解Java中的锁就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

线程 队列 尝试 头部 同时 成功 元素 方法 标记 内容 又称 指向 数据 文章 时间 更多 次数 知识 示例 篇文章 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 服务器热备份 数据库驱动和数据库的区别 公安基层网络安全防范 苹果13提示无法连接服务器 上海嵌入式软件开发项目管理 苹果手机怎么老是连接服务器失败 软件开发工资过高 数据库查资料 计算机信息与网络技术是干啥的 服务器上打不出数字按哪个键 如何提高软件开发与可靠性 微结互联网络科技武汉有限公司 数据库中计算变量的字符串长度 军营网络安全宣传周黑板报 最新数据库管理系统编程技术 2021浙江省网络安全竞赛 全国工会财务核算软件数据库类型 下拉列表访问数据库java 软件开发费用怎么入账 中国服务器市场在哪里 学校信息化网络安全分析 苹果手机怎么老是连接服务器失败 服务器维护流程 港版ipad如何设置大陆服务器 计算机网络技术大学考研分数线 数据库的开发控制完整性 4K电视网络安全 数据库技术计算机三级 如何保护数据库安全性 国际信息技术服务发展数据库
0