iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >浅谈Java并发之同步器设计
  • 129
分享到

浅谈Java并发之同步器设计

2024-04-02 19:04:59 129人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

前言: 在 java并发内存模型详情了解到多进程(线程)读取共享资源的时候存在竞争条件。 计算机中通过设计同步器来协调进程(线程)之间执行顺序。同步器作用就像登机安检人员一样可以协

前言:

java并发内存模型详情了解到多进程(线程)读取共享资源的时候存在竞争条件。

计算机中通过设计同步器来协调进程(线程)之间执行顺序。同步器作用就像登机安检人员一样可以协调旅客按顺序通过。

Java中,同步器可以理解为一个对象,它根据自身状态协调线程的执行顺序。比如Lock),信号量(Semaphore),屏障(CyclicBarrier),阻塞队列(Blocking Queue)。

这些同步器在功能设计上有所不同,但是内部实现上有共通的地方。

1、同步器

同步器的设计一般包含几个方面:状态变量设计(同步器内部状态),访问条件设定,状态更新,等待方式,通知策略。

访问条件是控制线程是否能执行(访问共享对象)的条件,它往往与状态变量紧密相关。而通知策略是线程释放锁定状态后通知其它等待线程的方式,一般有以下几种情况:

  • 通知所有等待的线程。
  • 通知1个随机的N个等待线程。
  • 通知1个特定的N个等待线程

看下面例子,通过锁方式的同步器


public class Lock{
  // 状态变量 isLocked
  private boolean isLocked = false; 
  public synchronized void lock() throws InterruptedException{
    // 访问条件 当isLocked=false 时获得访问权限否则等待
    while(isLocked){
      // 阻塞等待
      wait();
    }
    //状态更新 线程获得访问权限
    isLocked = true;
  }
  
  public synchronized void unlock(){
    //状态更新 线程释放访问权限
    isLocked = false;
    // 通知策略 object.notify | object.notifyAll
    notify(); 
  }
}

我们用计数信号量控制同时执行操作活动数。这里模拟一个连接池。


public class PoolSemaphore {
   // 状态变量 actives 计数器
    private int actives = 0;
    private int max;
    public PoolSemaphore(int max) {
        this.max = max;
    }
    public synchronized void acquire() throws InterruptedException {
        //访问条件 激活数小于最大限制时,获得访问权限否则等待
        while (this.actives == max) wait();
        //状态更新 线程获得访问权限
        this.actives++;
        // 通知策略 object.notify | object.notifyAll
        this.notify();
    }
    public synchronized void release() throws InterruptedException {
        //访问条件 激活数不为0时,获得访问权限否则等待
        while (this.actives == 0) wait();
         //状态更新 线程获得访问权限
        this.actives--;
        // 通知策略 object.notify | object.notifyAll
        this.notify();
    }
}

1.1 原子指令

同步器设计里面,最重要的操作逻辑是“如果满足条件,以更新状态变量来标志线程获得或释放访问权限”,该操作应具备原子性

比如test-and-set 计算机原子指令,意思是进行条件判断满足则设置新值。


function Lock(boolean *lock) { 
    while (test_and_set(lock) == 1); 
}


另外还有很多原子指令 fetch-and-add compare-and-swap,注意这些指令需硬件支持才有效。

同步操作中,利用计算机原子指令,可以避开锁,提升效率。java中没有 test-and-set 的支持,不过 java.util.concurrent.atomic 给我们提供了很多原子类api,里面支持了 getAndSet compareAndSet 操作。

看下面例子,主要在区别是等待方式不一样,上面是通过wait()阻塞等待,下面是无阻塞循环。


public class Lock{
  // 状态变量 isLocked
  private AtomicBoolean isLocked = new AtomicBoolean(false);
  public void lock() throws InterruptedException{
    // 等待方式 变为自旋等待
    while(!isLocked.compareAndSet(false, true));
    //状态更新 线程获得访问权限
    isLocked.set(true);
  }
  
  public synchronized void unlock(){
    //状态更新 线程释放访问权限
    isLocked.set(false);
  }
}

1.2 关于阻塞扩展说明

阻塞意味着需要将进程或线程状态进行转存,以便还原后恢复执行。这种操作是昂贵繁重,而线程基于进程之上相对比较轻量。线程的阻塞在不同编程平台实现方式也有所不同,像Java是基于JVM运行,所以它由JVM完成实现。

在《Java Concurrency in Practice》中,作者提到

竞争性同步可能需要OS活动,这增加了成本。当争用锁时,未获取锁的线程必须阻塞。 JVM可以通过旋转等待(反复尝试获取锁直到成功)来实现阻塞,也可以通过操作系统挂起阻塞的线程来实现阻塞。哪种效率更高取决于上下文切换开销与锁定可用之前的时间之间的关系。对于短暂的等待,最好使用自旋等待;对于长时间的等待,最好使用暂停。一些JVM基于对过去等待时间的分析数据来自适应地在这两者之间进行选择,但是大多数JVM只是挂起线程等待锁定。

从上面可以看出JVM实现阻塞两种方式

  • 旋转等待(spin-waiting),简单理解是不暂停执行以循环的方式等待,适合短时间场景。
  • 通过操作系统挂起线程。

JVM中通过 -XX: +UseSpinning 开启旋转等待, -XX: PreBlockSpi =10指定最大旋转次数。

2、AQS

AQSAbstractQueuedSynchronizer简称。本节对AQS只做简单阐述,并不全面。

java.util.concurrent包中的 ReentrantLockCountDownLatchSemaphoreCyclicBarrier等都是基于是AQS同步器实现。

状态变量 是用 int state 来表示,状态的获取与更新通过以下API操作。


 int getState()
void setState(int newState)
boolean compareAndSetState(int expect, int update)


该状态值在不同API中有不同表示意义。比如ReentrantLock中表示持有锁的线程获取锁的次数,Semaphore表示剩余许可数。

关于等待方式和通知策略的设计

AQS通过维护一个FIFO同步队列(Sync queue)来进行同步管理。当多线程争用共享资源时被阻塞入队。而线程阻塞与唤醒是通过 LockSupport.park/unpark API实现。

它定义了两种资源共享方式。

  • Exclusive(独占,只有一个线程能执行,如ReentrantLock
  • Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch

每个节点包含waitStatus(节点状态),prev(前继),next(后继),thread(入队时线程),nextWaitercondition队列的后继节点)

waitStatus 有以下取值

  • CANCELLED(1) 表示线程已取消。当发生超时或中断,节点状态变为取消,之后状态不再改变。
  • SIGNAL(-1) 表示后继节点等待前继的唤醒。后继节点入队时,会将前继状态更新为SIGNAL。
  • CONDITION(-2) 表示线程在Condition queue 里面等待。当其他线程调用了Condition.signal()方法后,CONDITION状态的节点将从 Condition queue 转移到 Sync queue,等待获取锁。
  • PROPAGATE(-3) 在共享模式下,当前节点释放后,确保有效通知后继节点。
  • (0) 节点加入队列时的默认状态。

AQS 几个关键 API

  • tryAcquire(int) 独占方式下,尝试去获取资源。成功返回true,否则false
  • tryRelease(int) 独占方式下,尝试释放资源,成功返回true,否则false
  • tryAcquireShared(int) 共享方式下,尝试获取资源。返回负数为失败,零和正数为成功并表示剩余资源。
  • tryReleaseShared(int) 共享方式下,尝试释放资源,如果释放后允许唤醒后续等待节点返回true,否则false
  • isHeldExclusively() 判断线程是否正在独占资源。

2.1 acquire(int arg)


public final void acquire(int arg) {
    if (
      // 尝试直接去获取资源,如果成功则直接返回
      !tryAcquire(arg)
        &&
        //线程阻塞在同步队列等待获取资源。等待过程中被中断,则返回true,否则false
        acquireQueued(
          // 标记该线程为独占方式,并加入同步队列尾部。
          addWaiter(node.EXCLUSIVE), arg) 
       )
        selfInterrupt();
}

2.2 release(int arg)


public final boolean release(int arg) {
   // 尝试释放资源
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
          // 唤醒下一个线程(后继节点)
          unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
  ....
     Node s = node.next; // 找到后继节点
        if (s == null || s.waitStatus > 0) {//无后继或节点已取消
            s = null;
           // 找到有效的等待节点 
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 唤醒线程
    }

总结:

文章记录并发编程中同步器设计的一些共性特征。并简单介绍了Java中的AQS。

到此这篇关于浅谈Java并发之同步器设计的文章就介绍到这了,更多相关Java并发之同步器设计内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 浅谈Java并发之同步器设计

本文链接: https://www.lsjlt.com/news/155125.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 浅谈Java并发之同步器设计
    前言: 在 Java并发内存模型详情了解到多进程(线程)读取共享资源的时候存在竞争条件。 计算机中通过设计同步器来协调进程(线程)之间执行顺序。同步器作用就像登机安检人员一样可以协...
    99+
    2022-11-12
  • 浅谈JAVA并发之ReentrantLock
    目录1. 介绍2. 源码剖析2.1 上锁(获取资源)2.2 释放资源2.3 公平锁与非公平锁的区别1. 介绍 结合上面的ReentrantLock类图,ReentrantLock实...
    99+
    2022-11-12
  • Java并发编程之浅谈ReentrantLock
    目录一、首先看图二、lock()跟踪源码2.1 非公平锁实现2.1.1 tryAcquire(arg)2.1.2 acquireQueued(addWaiter(Node.EXCLU...
    99+
    2022-11-12
  • Java并发之同步器设计的方法是什么
    本篇内容介绍了“Java并发之同步器设计的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在 Java并发之内存模型了解到多进程(线...
    99+
    2023-06-16
  • 浅谈同步监视器之同步代码块、同步方法
    如果有多个线程访问共享资源,可能会出现当一个线程没有处理完业务,然后另一个线程进入,从而导致共享资源出现不安全的情况。日常例子:银行取钱,A和B有拥有同一个银行账户,A用存折在柜台取钱,B在取款机取钱。取钱有两个关键步骤:(1)判断账户里的...
    99+
    2023-05-31
    同步监视器 同步代码块 同步方法
  • 浅谈Java设计模式之七大设计原则
    目录前言一、单一职责原则(SingleResponsibilityPrinciple,SRP)二、开闭原则(Open-ClosedPrinciple,OCP)三、里氏代换原则(Lis...
    99+
    2022-11-12
  • java设计模式之浅谈适配器模式
    目录一、结构型模式二、适配器模式三、什么是适配器模式?四、角色分析五、对象适配器有哪些优点六、类适配器缺点七、使用场景一、结构型模式 结构型模式有什么好处? 从程序的结构上实现松耦合...
    99+
    2022-11-12
  • Java设计模式之浅谈外观模式
    目录简介外观模式之理解实例①、定义子系统②、外观类③、测试好文推荐简介 外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类...
    99+
    2022-11-13
  • Java并发编程之同步容器
    目录简介一、什么是同步容器二、为什么要有同步容器三、同步容器的优缺点四、同步容器的使用场景总结简介 同步容器主要分两类,一种是Vector这样的普通类,一种是通过Collection...
    99+
    2022-11-12
  • 浅谈Java开发架构之领域驱动设计DDD落地
    目录一、前言二、开发目标三、服务架构3.1、应用层{application}3.2、领域层{domain}3.3、基础层{infrastructrue}3.4、接口层{interfa...
    99+
    2022-11-12
  • Java设计模式之浅谈模板方法模式
    一. 什么是模板方法设计模式 从字面意义上理解, 模板方法就是定义出来一套方法, 作为模板, 也就是基础。 在这个基础上, 我们可以进行加工,实现个性化的实现。比如:一日餐三. 早餐...
    99+
    2022-11-12
  • 浅谈Java设计模式之开放封闭原则
    写在前面最近, 接手了一个新业务,系统的架构可圈可点。但有些地方让人望而生畏,有些代码臃肿难以维护,让人不敢恭维。于是,结合了Java的开放封闭原则,对其中一部分代码进行了重构优化。先来看下以前系统的老代码ShareChannelManag...
    99+
    2023-05-31
    开放 封闭 ava
  • Java并发编程之同步容器与并发容器详解
    一、同步容器  1、Vector——>ArrayList  vector 是线程(Thread)同步(Synchronized)的,所以它也是线程...
    99+
    2022-11-12
  • java并发编程之同步器代码示例
    同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作。最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier和Exchanger队列同步器AbstractQueuedSynchronizer是用来...
    99+
    2023-05-30
    java 并发 同步器
  • 浅谈Java设计模式之原型模式知识总结
    如何使用? 1.首先定义一个User类,它必须实现了Cloneable接口,重写了clone()方法。 public class User implements Cloneabl...
    99+
    2022-11-12
  • Java并发编程之同步容器与并发容器的示例分析
    这篇文章主要为大家展示了“Java并发编程之同步容器与并发容器的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java并发编程之同步容器与并发容器的示例分析”这篇文章吧。一、同步容器&n...
    99+
    2023-06-15
  • Java并发编程同步器CountDownLatch
    CountDownLatch 在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在 CountDownLatch...
    99+
    2022-11-13
  • Java并发编程之详解CyclicBarrier线程同步
    CyclicBarrier线程同步 java.util.concurrent.CyclicBarrier提供了一种多线程彼此等待的同步机制,可以把它理解成一个障碍,所有先到达这个障碍...
    99+
    2022-11-12
  • Java并发编程之JUC并发核心AQS同步队列原理剖析
    目录一、AQS介绍二、AQS中的队列1、同步等待队列2、条件等待队列3、AQS队列节点Node三、同步队列源码分析1、同步队列分析2、同步队列——独占模式源码分析3、同步队列——共享...
    99+
    2022-11-12
  • Java并发代码设计的步骤是什么
    这篇文章主要介绍“Java并发代码设计的步骤是什么”,在日常操作中,相信很多人在Java并发代码设计的步骤是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java并发代码设计的步骤是什么”的疑惑有所帮助!...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作