首页 文章 精选 留言 我的

精选列表

搜索[青少年编程],共10003篇文章
优秀的个人博客,低调大师

python编程语言基础

今天给大家讲解python语言基础~~ 01、python核心数据类型 整型数 int:整数是不带有小数部分的数字 浮点型数 float:浮点数是带有小数部分的数字(小数部分可以是0) 复数 complex: 分为两部分:实部(real)、虚部(image) 虚部是以j或J结尾的数 布尔型数 bool:用来表求真和假两种状态的数字 True 表示值(条件满足或成立) False 表示假(条件不满足或不成立) 内置对象:空值 None 是一个表示不存的特殊对象 作用:用来占位、变量解除绑定 02、python算数运算符 在学习中有迷茫不知如何学习的朋友小编推荐一个学Python的学习q u n 227 -435- 450可以来了解一起进步一起学习!免费分享视频资料 Python入门 运算符: 算术运算符: + 加法 - 减法 * 乘法 / 除法 // 地板除floordiv % 求余 ** 幂运算 / 除法 除法得到的数是浮点型数,不会丢弃小数部分 8 / 2 得 4.0 // 地板除 除的结果云掉小数部分向下取整 如: 7 // 3 # 得 2 % 求余 如: 7 % 3 # 得 1 ** 幂运算 格式: x ** y 意为 x的y次方 如: 4 ** 2 # 得 16 运算符的优先级 ** * / // % + - 括号分组子表达式: ()内部的表达式先进行计算 混合类型自动升级 1 + 2.14 返回类型为浮点型 3.14 3 + 4.0 返回 7.0 03、变量 什么是变量? 变量是关联一个对象的"标识符"。 标识符的命名规则: 必须为字母或下划线开头,后跟字母或下划线或数字; 不能使用python的关键字。 说明: python的变量名区分大小写 合法的标识符(变量名) 举例 : abc a1b2 _abc __Abcd var 不合法的标识符: 1a2b $ABC +a -b #ABC @ ABC python关键字: True, False, None, is, del, if, elif ...等 04、赋值语句 赋值语句 = 语法: 变量名 = 表达式 或 变量名1 = 变量名2 = 表达式 或 变量名1, 变量名2 = 序列 作用: 1. 创建一个变量,此变量用来绑定数据对象 2. 修改一个变量的绑定关系,让其绑定另一个数据对象 说明: 当变量不存在时,创建该变量,同时将变量绑定在这个对象上 当变量存在时,改变此变量绑定的对象 一个变量只能绑定一个对象 两个变量可以同时绑定同一个对象

优秀的个人博客,低调大师

Java并发编程-AQS

文章耗时一个月,所以篇幅有点长,需要一点耐心。 1、AQS产生背景 通过JCP的JSR166规范,Jdk1.5开始引入了j.u.c包,这个包提供了一系列支持并发的组件。这些组件是一系列的同步器,这些同步器主要维护着以下几个功能:内部同步状态的管理(例如表示一个锁的状态是获取还是释放),同步状态的更新和检查操作,且至少有一个方法会导致调用线程在同步状态被获取时阻塞,以及在其他线程改变这个同步状态时解除线程的阻塞。上述的这些的实际例子包括:互斥排它锁的不同形式、读写锁、信号量、屏障、Future、事件指示器以及传送队列等。可以看下这里的4.2的图便能理解j.u.c包的组件构成。 几乎任一同步器都可以用来实现其他形式的同步器。例如,可以用可重入锁实现信号量或者用信号量实现可重入锁。但是,这样做带来的复杂性、开销及不灵活使j.u.c最多只能是一个二流工程,且缺乏吸引力。如果任何这样的构造方式不能在本质上比其他形式更简洁,那么开发者就不应该随意地选择其中的某个来构建另一个同步器。因此,JSR166基于AQS类建立了一个小框架,这个框架为构造同步器提供一种通用的机制,并且被j.u.c包中大部分类使用,同时很多用户也可以用它来定义自己的同步器。这个就是j.u.c的作者Doug Lea大神的初衷,通过提供AQS这个基础组件来构建j.u.c的各种工具类,至此就可以理解AQS的产生背景了。 2、AQS的设计和结构 2.1设计思想 同步器的核心方法是acquire和release操作,其背后的思想也比较简洁明确。acquire操作是这样的: while (当前同步器的状态不允许获取操作) { 如果当前线程不在队列中,则将其插入队列 阻塞当前线程 } 如果线程位于队列中,则将其移出队列 release操作是这样的: 更新同步器的状态 if (新的状态允许某个被阻塞的线程获取成功) 解除队列中一个或多个线程的阻塞状态 从这两个操作中的思想中我们可以提取出三大关键操作:同步器的状态变更、线程阻塞和释放、插入和移出队列。所以为了实现这两个操作,需要协调三大关键操作引申出来的三个基本组件: ·同步器状态的原子性管理; ·线程阻塞与解除阻塞; ·队列的管理; 由这三个基本组件,我们来看j.u.c是怎么设计的。 2.1.1同步状态 AQS类使用单个int(32位)来保存同步状态,并暴露出getState、setState以及compareAndSet操作来读取和更新这个同步状态。其中属性state被声明为volatile,并且通过使用CAS指令来实现compareAndSetState,使得当且仅当同步状态拥有一个一致的期望值的时候,才会被原子地设置成新值,这样就达到了同步状态的原子性管理,确保了同步状态的原子性、可见性和有序性。 基于AQS的具体实现类(如锁、信号量等)必须根据暴露出的状态相关的方法定义tryAcquire和tryRelease方法,以控制acquire和release操作。当同步状态满足时,tryAcquire方法必须返回true,而当新的同步状态允许后续acquire时,tryRelease方法也必须返回true。这些方法都接受一个int类型的参数用于传递想要的状态。 2.1.2阻塞 直到JSR166,阻塞线程和解除线程阻塞都是基于Java的内置管程,没有其它非基于Java内置管程的API可以用来达到阻塞线程和解除线程阻塞。唯一可以选择的是Thread.suspend和Thread.resume,但是它们都有无法解决的竞态问题,所以也没法用,目前该方法基本已被抛弃。具体不能用的原因可以官方给出的答复。 j.u.c.locks包提供了LockSupport类来解决这个问题。方法LockSupport.park阻塞当前线程直到有个LockSupport.unpark方法被调用。unpark的调用是没有被计数的,因此在一个park调用前多次调用unpark方法只会解除一个park操作。另外,它们作用于每个线程而不是每个同步器。一个线程在一个新的同步器上调用park操作可能会立即返回,因为在此之前可以有多余的unpark操作。但是,在缺少一个unpark操作时,下一次调用park就会阻塞。虽然可以显式地取消多余的unpark调用,但并不值得这样做。在需要的时候多次调用park会更高效。park方法同样支持可选的相对或绝对的超时设置,以及与JVM的Thread.interrupt结合 ,可通过中断来unpark一个线程。 2.1.3队列 整个框架的核心就是如何管理线程阻塞队列,该队列是严格的FIFO队列,因此不支持线程优先级的同步。同步队列的最佳选择是自身没有使用底层锁来构造的非阻塞数据结构,业界主要有两种选择,一种是MCS锁,另一种是CLH锁。其中CLH一般用于自旋,但是相比MCS,CLH更容易实现取消和超时,所以同步队列选择了CLH作为实现的基础。 CLH队列实际并不那么像队列,它的出队和入队与实际的业务使用场景密切相关。它是一个链表队列,通过AQS的两个字段head(头节点)和tail(尾节点)来存取,这两个字段是volatile类型,初始化的时候都指向了一个空节点。如下图: 入队操作:CLH队列是FIFO队列,故新的节点到来的时候,是要插入到当前队列的尾节点之后。试想一下,当一个线程成功地获取了同步状态,其他线程将无法获取到同步状态,转而被构造成为节点并加入到同步队列中,而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个CAS方法,它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。入队操作示意图大致如下: 出队操作:因为遵循FIFO规则,所以能成功获取到AQS同步状态的必定是首节点,首节点的线程在释放同步状态时,会唤醒后续节点,而后续节点会在获取AQS同步状态成功的时候将自己设置为首节点。设置首节点是由获取同步成功的线程来完成的,由于只能有一个线程可以获取到同步状态,所以设置首节点的方法不需要像入队这样的CAS操作,只需要将首节点设置为原首节点的后续节点同时断开原节点、后续节点的引用即可。出队操作示意图大致如下: 这一小节只是简单的描述了队列的大概,目的是为了表达清楚队列的设计框架,实际上CLH队列已经和初始的CLH队列已经发生了一些变化,具体的可以看查看资料中Doug Lea的那篇论文中的3.3 Queues。 2.1.4条件队列 上一节的队列其实是AQS的同步队列,这一节的队列是条件队列,队列的管理除了有同步队列,还有条件队列。AQS只有一个同步队列,但是可以有多个条件队列。AQS框架提供了一个ConditionObject类,给维护独占同步的类以及实现Lock接口的类使用。 ConditionObject类实现了Condition接口,Condition接口提供了类似Object管程式的方法,如await、signal和signalAll操作,还扩展了带有超时、检测和监控的方法。ConditionObject类有效地将条件与其它同步操作结合到了一起。该类只支持Java风格的管程访问规则,这些规则中,当且仅当当前线程持有锁且要操作的条件(condition)属于该锁时,条件操作才是合法的。这样,一个ConditionObject关联到一个ReentrantLock上就表现的跟内置的管程(通过Object.wait等)一样了。两者的不同仅仅在于方法的名称、额外的功能以及用户可以为每个锁声明多个条件。 ConditionObject类和AQS共用了内部节点,有自己单独的条件队列。signal操作是通过将节点从条件队列转移到同步队列中来实现的,没有必要在需要唤醒的线程重新获取到锁之前将其唤醒。signal操作大致示意图如下: await操作就是当前线程节点从同步队列进入条件队列进行等待,大致示意图如下: 实现这些操作主要复杂在,因超时或Thread.interrupt导致取消了条件等待时,该如何处理。await和signal几乎同时发生就会有竞态问题,最终的结果遵照内置管程相关的规范。JSR133修订以后,就要求如果中断发生在signal操作之前,await方法必须在重新获取到锁后,抛出InterruptedException。但是,如果中断发生在signal后,await必须返回且不抛异常,同时设置线程的中断状态。 2.2方法结构 如果我们理解了上一节的设计思路,我们大致就能知道AQS的主要数据结构了。 组件 数据结构 同步状态 volatile int state 阻塞 LockSupport类 队列 Node节点 条件队列 ConditionObject 进而再来看下AQS的主要方法及其作用。 属性、方法 描述、作用 int getState() 获取当前同步状态 void setState(int newState) 设置当前同步状态 boolean compareAndSetState(int expect, int update) 通过CAS设置当前状态,此方法保证状态设置的原子性 boolean tryAcquire(int arg) 钩子方法,独占式获取同步状态,AQS没有具体实现,具体实现都在子类中,实现此方法需要查询当前同步状态并判断同步状态是否符合预期,然后再CAS设置同步状态 boolean tryRelease(int arg) 钩子方法,独占式释放同步状态,AQS没有具体实现,具体实现都在子类中,等待获取同步状态的线程将有机会获取同步状态 int tryAcquireShared(int arg) 钩子方法,共享式获取同步状态,AQS没有具体实现,具体实现都在子类中,返回大于等于0的值表示获取成功,反之失败 boolean tryReleaseShared(int arg) 钩子方法,共享式释放同步状态,AQS没有具体实现,具体实现都在子类中 boolean isHeldExclusively() 钩子方法,AQS没有具体实现,具体实现都在子类中,当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占 void acquire(int arg) 模板方法,独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则会进入同步队列等待,此方法会调用子类重写的tryAcquire方法 void acquireInterruptibly(int arg) 模板方法,与acquire相同,但是此方法可以响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,此方法会抛出InterruptedException并返回 boolean tryAcquireNanos(int arg, long nanosTimeout) 模板方法,在acquireInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,则会返回false,如果获取到了则会返回true boolean release(int arg) 模板方法,独占式的释放同步状态,该方法会在释放同步状态后,将同步队列中的第一个节点包含的线程唤醒 void acquireShared(int arg) 模板方法,共享式的获取同步状态,如果当前系统未获取到同步状态,将会进入同步队列等待,与acquire的主要区别在于同一时刻可以有多个线程获取到同步状态 void acquireSharedInterruptibly(int arg) 模板方法,与acquireShared一致,但是可以响应中断 boolean tryAcquireSharedNanos(int arg, long nanosTimeout) 模板方法,在acquireSharedInterruptibly基础上增加了超时限制 boolean releaseShared(int arg) 模板方法,共享式的释放同步状态 Collection<Thread> getQueuedThreads() 模板方法,获取等待在同步队列上的线程集合 Node int waitStatus 等待状态 1、 CANCELLED,值为1,在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,节点进入该状态后将不会变化; 2、 SIGNAL,值为-1,后续节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后续节点,使后续节点的线程得以运行; 3、 CONDITION,值为-2,节点在条件队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法后,该节点将会从条件队列中转移到同步队列中,加入到对同步状态的获取中; 4、 PROPAGATE,值为-3,表示下一次共享式同步状态获取将会无条件地传播下去 Node prev 前驱节点,当节点加入同步队列时被设置 Node next 后续节点 Thread thread 获取同步状态的线程 Node nextWaiter 条件队列中的后续节点,如果当前节点是共享的,那么这个字段将是一个SHARED变量,也就是说节点类型(独占和共享)和条件队列中的后续节点共用同一个字段 LockSupport void park() 阻塞当前线程,如果调用unpark方法或者当前线程被中断,才能从park方法返回 LockSupport void unpark(Thread thread) 唤醒处于阻塞状态的线程 ConditionObject Node firstWaiter 条件队列首节点 ConditionObject Node lastWaiter 条件队列尾节点 void await() 当前线程进入等待状态直到signal或中断,当前线程将进入运行状态且从await方法返回的情况,包括: 其他线程调用该Condition的signal或者signalAll方法,且当前线程被选中唤醒; 其他线程调用interrupt方法中断当前线程; 如果当前线程从await方法返回表明该线程已经获取了Condition对象对应的锁 void awaitUninterruptibly() 和await方法类似,但是对中断不敏感 long awaitNanos(long nanosTimeout) 当前线程进入等待状态直到被signal、中断或者超时。返回值表示剩余的时间。 boolean awaitUntil(Date deadline) 当前线程进入等待状态直到被signal、中断或者某个时间。如果没有到指定时间就被通知,方法返回true,否则表示到了指定时间,返回false void signal() 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁 void signalAll() 唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁 看到这,我们对AQS的数据结构应该基本上有一个大致的认识,有了这个基本面的认识,我们就可以来看下AQS的源代码。 3、AQS的源代码实现 主要通过独占式同步状态的获取和释放、共享式同步状态的获取和释放来看下AQS是如何实现的。 3.1独占式同步状态的获取和释放 独占式同步状态调用的方法是acquire,代码如下: public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 上述代码主要完成了同步状态获取、节点构造、加入同步队列以及在同步队列中自旋等待的相关工作,其主要逻辑是:首先调用子类实现的tryAcquire方法,该方法保证线程安全的获取同步状态,如果同步状态获取失败,则构造独占式同步节点(同一时刻只能有一个线程成功获取同步状态)并通过addWaiter方法将该节点加入到同步队列的尾部,最后调用acquireQueued方法,使得该节点以自旋的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。 下面来首先来看下节点构造和加入同步队列是如何实现的。代码如下: private Node addWaiter(Node mode) { // 当前线程构造成Node节点 Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure // 尝试快速在尾节点后新增节点 提升算法效率 先将尾节点指向pred Node pred = tail; if (pred != null) { //尾节点不为空 当前线程节点的前驱节点指向尾节点 node.prev = pred; //并发处理 尾节点有可能已经不是之前的节点 所以需要CAS更新 if (compareAndSetTail(pred, node)) { //CAS更新成功 当前线程为尾节点 原先尾节点的后续节点就是当前节点 pred.next = node; return node; } } //第一个入队的节点或者是尾节点后续节点新增失败时进入enq enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize //尾节点为空 第一次入队 设置头尾节点一致 同步队列的初始化 if (compareAndSetHead(new Node())) tail = head; } else { //所有的线程节点在构造完成第一个节点后 依次加入到同步队列中 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } 节点进入同步队列之后,就进入了一个自旋的过程,每个线程节点都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中并会阻塞节点的线程,代码如下: final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //获取当前线程节点的前驱节点 final Node p = node.predecessor(); //前驱节点为头节点且成功获取同步状态 if (p == head && tryAcquire(arg)) { //设置当前节点为头节点 setHead(node); p.next = null; // help GC failed = false; return interrupted; } //是否阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } 再来看看shouldParkAfterFailedAcquire和parkAndCheckInterrupt是怎么来阻塞当前线程的,代码如下: private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //前驱节点的状态决定后续节点的行为 int ws = pred.waitStatus; if (ws == Node.SIGNAL) /*前驱节点为-1 后续节点可以被阻塞 * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /*前驱节点是初始或者共享状态就设置为-1 使后续节点阻塞 * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { //阻塞线程 LockSupport.park(this); return Thread.interrupted(); } 节点自旋的过程大致示意图如下,其实就是对图二、图三的补充。 图六 节点自旋获取队列同步状态 整个独占式获取同步状态的流程图大致如下: 图七 独占式获取同步状态 当同步状态获取成功之后,当前线程从acquire方法返回,对于锁这种并发组件而言,就意味着当前线程获取了锁。有获取同步状态的方法,就存在其对应的释放方法,该方法为release,现在来看下这个方法的实现,代码如下: 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) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /*寻找符合条件的后续节点 * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ 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); } 独占式释放是非常简单而且明确的。 总结下独占式同步状态的获取和释放:在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease方法释放同步状态,然后唤醒头节点的后继节点。 3.2共享式同步状态的获取和释放 共享式同步状态调用的方法是acquireShared,代码如下: public final void acquireShared(int arg) { //获取同步状态的返回值大于等于0时表示可以获取同步状态 //小于0时表示可以获取不到同步状态 需要进入队列等待 if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } private void doAcquireShared(int arg) { //和独占式一样的入队操作 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; //自旋 for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { //前驱结点为头节点且成功获取同步状态 可退出自旋 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below //退出自旋的节点变成首节点 setHead(node); /* * Try to signal next queued node if: * Propagation was indicated by caller, * or was recorded (as h.waitStatus either before * or after setHead) by a previous operation * (note: this uses sign-check of waitStatus because * PROPAGATE status may transition to SIGNAL.) * and * The next node is waiting in shared mode, * or we don't know, because it appears null * * The conservatism in both of these checks may cause * unnecessary wake-ups, but only when there are multiple * racing acquires/releases, so most need signals now or soon * anyway. */ if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } } 与独占式一样,共享式获取也需要释放同步状态,通过调用releaseShared方法可以释放同步状态,代码如下: public final boolean releaseShared(int arg) { //释放同步状态 if (tryReleaseShared(arg)) { //唤醒后续等待的节点 doReleaseShared(); return true; } return false; } private void doReleaseShared() { /* * Ensure that a release propagates, even if there are other * in-progress acquires/releases. This proceeds in the usual * way of trying to unparkSuccessor of head if it needs * signal. But if it does not, status is set to PROPAGATE to * ensure that upon release, propagation continues. * Additionally, we must loop in case a new node is added * while we are doing this. Also, unlike other uses of * unparkSuccessor, we need to know if CAS to reset status * fails, if so rechecking. */ //自旋 for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases //唤醒后续节点 unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } } unparkSuccessor方法和独占式是一样的。 4、AQS应用 AQS被大量的应用在了同步工具上。 ReentrantLock:ReentrantLock类使用AQS同步状态来保存锁重复持有的次数。当锁被一个线程获取时,ReentrantLock也会记录下当前获得锁的线程标识,以便检查是否是重复获取,以及当错误的线程试图进行解锁操作时检测是否存在非法状态异常。ReentrantLock也使用了AQS提供的ConditionObject,还向外暴露了其它监控和监测相关的方法。 ReentrantReadWriteLock:ReentrantReadWriteLock类使用AQS同步状态中的16位来保存写锁持有的次数,剩下的16位用来保存读锁的持有次数。WriteLock的构建方式同ReentrantLock。ReadLock则通过使用acquireShared方法来支持同时允许多个读线程。 Semaphore:Semaphore类(信号量)使用AQS同步状态来保存信号量的当前计数。它里面定义的acquireShared方法会减少计数,或当计数为非正值时阻塞线程;tryRelease方法会增加计数,在计数为正值时还要解除线程的阻塞。 CountDownLatch:CountDownLatch类使用AQS同步状态来表示计数。当该计数为0时,所有的acquire操作(对应到CountDownLatch中就是await方法)才能通过。 FutureTask:FutureTask类使用AQS同步状态来表示某个异步计算任务的运行状态(初始化、运行中、被取消和完成)。设置(FutureTask的set方法)或取消(FutureTask的cancel方法)一个FutureTask时会调用AQS的release操作,等待计算结果的线程的阻塞解除是通过AQS的acquire操作实现的。 SynchronousQueues:SynchronousQueues类使用了内部的等待节点,这些节点可以用于协调生产者和消费者。同时,它使用AQS同步状态来控制当某个消费者消费当前一项时,允许一个生产者继续生产,反之亦然。 除了这些j.u.c提供的工具,还可以基于AQS自定义符合自己需求的同步器。 AQS就学习到这,如果有描述不当的地方,还请留言交流。了解了AQS后下一步准备详细学习基于AQS的工具类。

优秀的个人博客,低调大师

Python网络编程 —— 进程

个人独立博客:www.limiao.tech 微信公众号:TechBoard 进程 进程:通俗理解就是一个运行的程序或者软件,进程是操作系统资源分配的基本单位 一个程序至少有一个进程,一个进程至少有一个线程,多进程可以完成多任务 进程的状态 工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致了有了不同的状态 进程的使用 导入进程模块: import multiprocessing 用进程完成多任务 import multiprocessing import time def sing(): for i in range(10): print("唱歌中...") time.sleep(0.2) def dance(): for i in range(10): print("跳舞中...") time.sleep(0.2) if __name__ == "__main__": # 创建对应的子进程执行对应的任务 sing_process = multiprocessing.Process(target=sing) dance_process = multiprocessing.Process(target=dance) # 启动进程执行对应的任务 sing_process.start() dance_process.start() Process类参数介绍 import multiprocessing import os def show_info(name,age): print("show_info:", multiprocessing.current_process()) # 获取进程的编号 pritn("show_info pid:", multiprocessing.current_process().pid, os.getpid) print(name, age) if __name__ == "__main__": # 创建子进程 # group: 进程组,目前只能使用None # target: 执行的目标任务 # args: 以元组方式传参 # kwargs: 以字典方式传参 sub_prcess = multiprocessing.Process(group=None, target=show_info, arg=("杨幂", 18)) sub_prcess.start() 进程之间不共享全局变量 import multiprocessing import time # 全局变量 g_list = [] # 添加数据 def add_data(): for i in range(15): g_list.append(i) time.sleep(0.1) print("add_data:", g_list) # 读取数据 def read_data(): print("read_data:", g_list) if __name__ == "__main__": # 创建添加数据的子进程 add_process = multiprocessing.Process(target=add_data) # 创建读取数据的子进程 read_process = multiprocessing.Process(target=read_data) # 启动进程 add_process.start() # 主进程等待添加数据的子进程执行完成以后再执行读取进程的操作 add_process.join() # 代码执行到此说明添加数据的子进程把任务执行完成了 read_process.start() 创建子进程其实就是对主进程资源的拷贝 主进程会等待所有的子进程执行完成程序再退出 import multiprocessing import time # 工作任务 def work(): for i in range(10): print("工作中...") time.sleep(0.3) if __name__ == "__main__": # 创建子进程 sub_prcess = multiprocessing.Process(target=work) # 查看进程的守护状态 # print(sub_prcess.daemon) # 守护主进程,主进程退出子进程直接销毁,不再执行子进程里面的代码 # sub_prcess.daemon = True # 启动进程执行对应的任务 sub_process.start() # 主进程延时1s time.sleep(1) print("主进程执行完了") # 主进程退出之前把所有的子进程销毁 sub_prcess.terminate() exit() 总结: 主进程会等待所有的子进程执行完成程序再退出 获取进程pid # 获取进程pid import multiprocessing import time import os def work(): # 获取当前进程编号 print("work进程编号:", os.getpid()) # 获取父进程编号 print("work父进程编号:", os.getppid()) for i in range(10): print("工作中...") time.sleep(1) # 扩展:根据进程编号杀死对应的进程 # os.kill(os.getpid(), 9) if __name__ == '__main__': # 获取当前进程的编号: print("当前进程编号:", multiprocessing.current_process().pid) # 创建子进程 sub_process = multiprocessing.Process(target=work) # 启动进程 sub_process.start() # 主进程执行打印信息操作 for i in range(20): print("我在主进程中执行...") time.sleep(1) 运行结果: 当前进程编号: 624 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... work进程编号: 1312 work父进程编号: 624 工作中... 工作中... 工作中... 工作中... 工作中... 工作中... 工作中... 工作中... 工作中... 工作中... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... 我在主进程中执行... ***Repl Closed*** 进程间通信——Queue 可以使用multiprocessing模块Queue实现多进程之间的数据传递,Queue本身是一个消息队列程序 import multiprocessing if __name__ == "__main__": # 创建消息队列 # 3:表示消息队列的最大个数 queue = multiprocessing.Queue(3) # 存放数据 queue.put(1) queue.put("hello") queue.put([1, 5, 8]) # 总结:队列可以放入任意类型的数据 # queue.put("xxx": "yyy") # 放入消息的时候不会进行等待,如果发现队列满了不能放入数据,那么会直接崩溃 # 建议: 放入数据统一使用 put 方法 # queue.put_nowait(("xxx": "yyy")) # 判断队列是否满了 result = queue.full() print(result) # 判断队列是否为空,不靠谱(加延时可解决) result = queue.empty() print("队列是否为空:", result) # 获取队列消息个数 size = queue.qsize() print("消息个数:", size) # 获取队列中的数据 res = queue.get() print(res) # 如果队列空了,那么使用get方法会等待队列有消息以后再取值 消息队列Queue完成进程间通信的演练 import multiprocessing import time # 添加数据 def add_data(queue): for i in range(5): # 判断队列是否满了 if queue.full(): # 如果满了跳出循环,不再添加数据 print("队列满了") break queue.put(i) print("add:", i) time.sleep(0.1) def read_data(queue): while True: if queue.qsize == 0: print("队列空了") break result = queue.get() print("read:", result) if __name__ == "__main__": # 创建消息队列 queue = multiprocessing.Queue(3) # 创建添加数据的子进程 add_process = multiprocessing.Process(target=add_data, args=(queue,)) # 创建读取数据的子进程 read_process = multiprocessing.Process(target=read_data, args=(queue,)) # 启动进程 add_process.start() # 主进程等待写入进程执行完成以后代码再继续往下执行 add_process.join() read_process.start() 进程池Pool 进程池的概念 池子里面放的是进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务 当需要创建的子进程数量不多时,可以直接利用multiprocess中的Process动态生成多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocess模块提供的Pool方法。 初始化Pool时,可以指定一个最大进程数,当有新的请求提到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求,但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。 进程池同步执行任务 进程池同步执行任务表示进程池中的进程在执行任务的时候一个执行完成另外一个才能执行,如果没有执行完会等待上一个进程执行 进程池同步实例代码 import multiprocessing import time # 拷贝任务 def work(): print("复制中...", multiprocessing.current_process().pid) time.sleep(1) if __name__ == '__main__': # 创建进程池 #3:进程池中进程的最大个数 pool = multiprocessing.Pool(3) # 模拟大批量的任务,让进程池去执行 for i in range(5): # 循环让进程池执行对应的work任务 # 同步执行任务,一个任务执行完成以后另外一个任务才能执行 pool.apply(work) 运行结果: 复制中... 6172 复制中... 972 复制中... 972 复制中... 1624 复制中... 1624 ***Repl Closed*** 进程池异步执行任务 进程池异步执行任务表示进程池中的进程同时执行任务,进程之间不会等待 进程池异步实例代码 import multiprocessing import time # 拷贝任务 def work(): print("复制中...", multiprocessing.current_process().pid) # 获取当前进程的守护状态 # 提示:使用进程池创建的进程时守护主进程的状态,默认自己通过Process创建的进程是不守护主进程的状态 # print(multiprocessing.current_process().daemon) time.sleep(1) if __name__ == '__main__': # 创建进程池 # 3:进程池中进程的最大个数 pool = multiprocessing.Pool(3) # 模拟大批量的任务,让进程池去执行 for i in range(5): # 循环让进程池执行对应的work任务 # 同步执行任务,一个任务执行完成以后另外一个任务才能执行 # pool.apply(work) # 异步执行,任务执行不会等待,多个任务一起执行 pool.apply_async(work) # 关闭进程池,意思告诉主进程以后不会有新的任务添加进来 pool.close() # 主进程等待进程池执行完成以后程序再退出 pool.join() 运行结果: 复制中... 1848 复制中... 12684 复制中... 12684 复制中... 6836 复制中... 6836 ***Repl Closed*** 个人独立博客:www.limiao.tech 微信公众号:TechBoard

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册