diff --git a/week_03/32/AbstractQueuedSynchronizer.md b/week_03/32/AbstractQueuedSynchronizer.md new file mode 100644 index 0000000000000000000000000000000000000000..15c83df6c379c40077bcd08965e632f343311a95 --- /dev/null +++ b/week_03/32/AbstractQueuedSynchronizer.md @@ -0,0 +1,504 @@ +## AbstractQueuedSynchronizer源码阅读 + +## 1.1 AbstractQueuedSynchronizer: + 1.1.1:AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,是一个同步器,用来设置占用lock资源的线程 + 1.1.2:AbstractQueuedSynchronizer实现了java.io.Serializable,说明可以进行序列化。 +## 1.2 AbstractQueuedSynchronizer内部类: + 1.2.1:Node:等待队列节点类 + 1.2.1.1:Node属性: +```java + static final Node SHARED = new Node(); //共享模式 + static final Node EXCLUSIVE = null; //独占模式 + static final int CANCELLED = 1; //当前线程被取消 + static final int SIGNAL = -1; //当前节点的后继节点要运行,也就是unpark + static final int CONDITION = -2; //当前节点在condition队列中等待 + static final int PROPAGATE = -3; //后继的acquireShared能够得以执行,读写锁和信号量使用 + volatile int waitStatus; //节点状态 + volatile Node prev; //上一节点 + volatile Node next; //后一节点 + volatile Thread thread; //节点绑定线程 + Node nextWaiter; //后一节点等待者 + +``` + 1.2.1.2:Node方法: + 1.2.1.2.1: +```java + final boolean isShared() { //当前节点是否处于共享模式等待 + return nextWaiter == SHARED; + } +``` + 1.2.1.2.2: +```java + final Node predecessor() throws NullPointerException { //获取前驱节点,如果为空的话抛出空指针异常 + Node p = prev; + if (p == null) + throw new NullPointerException(); + else + return p; + } +``` +##1.3 AbstractQueuedSynchronizer属性: +```java + private transient volatile Node head; //头节点 + private transient volatile Node tail; //尾节点 + private volatile int state; //同步状态 +``` + +##1.4 AbstractQueuedSynchronizer方法: + 1.4.1: +```java + protected final int getState() { //获取同步状态值 + return state; + } +``` + 1.4.2: +```java + protected final void setState(int newState) { //设置同步状态的值 + state = newState; + } +``` + 1.4.3: +```java + protected final boolean compareAndSetState(int expect, int update) { //原子操作更新同步状态值 + // See below for intrinsics setup to support this + return unsafe.compareAndSwapInt(this, stateOffset, expect, update); + } +``` + 1.4.4: +```java + private Node enq(final Node node) { //通过循环+CAS在队列中成功插入一个节点后返回。 + for (;;) { + Node t = tail; + if (t == null) { // 初始化head和tail + if (compareAndSetHead(new Node())) //cas操作头节点 + tail = head; + } else { + node.prev = t; + if (compareAndSetTail(t, node)) { // CAS设置tail为node,成功后把老的tail也就是t连接到node。 + t.next = node; + return t; + } + } + } + } +``` + 1.4.5: +```java + private Node addWaiter(Node mode) { //在队列中添加一个节点 + Node node = new Node(Thread.currentThread(), mode); + // Try the fast path of enq; backup to full enq on failure + Node pred = tail; + if (pred != null) { + node.prev = pred; + if (compareAndSetTail(pred, node)) { + pred.next = node; + return node; + } + } + enq(node); + return node; + } +``` + 1.4.6: +```java + private void setHead(Node node) { //设置头节点 + head = node; + node.thread = null; + node.prev = null; + } +``` + 1.4.7: +```java + private void unparkSuccessor(Node node) { //唤醒后继线程。 + int ws = node.waitStatus;// 获取node结点的等待状态 + if (ws < 0) + compareAndSetWaitStatus(node, ws, 0); //设置节点状态为0 + + Node s = node.next;// 获取node节点的下一个结点 + 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); //唤醒下一个节点 + } +``` + 1.4.8: +```java + +``` 1.4.1: +```java +``` + +## 获取独占锁的实现 +```java + /** + * 获取独占锁,对中断不敏感。 + * 首先尝试获取一次锁,如果成功,则返回; + * 否则会把当前线程包装成Node插入到队列中,在队列中会检测是否为head的直接后继,并尝试获取锁, + * 如果获取失败,则会通过LockSupport阻塞当前线程,直至被释放锁的线程唤醒或者被中断,随后再次尝试获取锁,如此反复。 + */ + public final void acquire(int arg) { + if (!tryAcquire(arg) && + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) + selfInterrupt(); + } + + /** + * 在队列中新增一个节点。 + */ + private Node addWaiter(Node mode) { + Node node = new Node(Thread.currentThread(), mode); + Node pred = tail; + // 快速尝试 + if (pred != null) { + node.prev = pred; + // 通过CAS在队尾插入当前节点 + if (compareAndSetTail(pred, node)) { + pred.next = node; + return node; + } + } + // 初始情况或者在快速尝试失败后插入节点 + enq(node); + return node; + } + + /** + * 通过循环+CAS在队列中成功插入一个节点后返回。 + */ + private Node enq(final Node node) { + for (;;) { + Node t = tail; + // 初始化head和tail + if (t == null) { + if (compareAndSetHead(new Node())) + tail = head; + } else { + /* + * AQS的精妙就是体现在很多细节的代码,比如需要用CAS往队尾里增加一个元素 + * 此处的else分支是先在CAS的if前设置node.prev = t,而不是在CAS成功之后再设置。 + * 一方面是基于CAS的双向链表插入目前没有完美的解决方案,另一方面这样子做的好处是: + * 保证每时每刻tail.prev都不会是一个null值,否则如果node.prev = t + * 放在下面if的里面,会导致一个瞬间tail.prev = null,这样会使得队列不完整。 + */ + node.prev = t; + // CAS设置tail为node,成功后把老的tail也就是t连接到node。 + 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(); + /* + * 检测当前节点前驱是否head,这是试获取锁的资格。 + * 如果是的话,则调用tryAcquire尝试获取锁, + * 成功,则将head置为当前节点。 + */ + if (p == head && tryAcquire(arg)) { + setHead(node); + p.next = null; // help GC + failed = false; + return interrupted; + } + /* + * 如果未成功获取锁则根据前驱节点判断是否要阻塞。 + * 如果阻塞过程中被中断,则置interrupted标志位为true。 + * shouldParkAfterFailedAcquire方法在前驱状态不为SIGNAL的情况下都会循环重试获取锁。 + */ + if (shouldParkAfterFailedAcquire(p, node) && + parkAndCheckInterrupt()) + interrupted = true; + } + } finally { + if (failed) + cancelAcquire(node); + } + } + + /** + * 根据前驱节点中的waitStatus来判断是否需要阻塞当前线程。 + */ + private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { + int ws = pred.waitStatus; + if (ws == Node.SIGNAL) + /* + * 前驱节点设置为SIGNAL状态,在释放锁的时候会唤醒后继节点, + * 所以后继节点(也就是当前节点)现在可以阻塞自己。 + */ + return true; + if (ws > 0) { + /* + * 前驱节点状态为取消,向前遍历,更新当前节点的前驱为往前第一个非取消节点。 + * 当前线程会之后会再次回到循环并尝试获取锁。 + */ + do { + node.prev = pred = pred.prev; + } while (pred.waitStatus > 0); + pred.next = node; + } else { + /** + * 等待状态为0或者PROPAGATE(-3),设置前驱的等待状态为SIGNAL, + * 并且之后会回到循环再次重试获取锁。 + */ + compareAndSetWaitStatus(pred, ws, Node.SIGNAL); + } + return false; + } + + + /** + * 该方法实现某个node取消获取锁。 + */ + private void cancelAcquire(Node node) { + + if (node == null) + return; + + node.thread = null; + + // 遍历并更新节点前驱,把node的prev指向前部第一个非取消节点。 + Node pred = node.prev; + while (pred.waitStatus > 0) + node.prev = pred = pred.prev; + + // 记录pred节点的后继为predNext,后续CAS会用到。 + Node predNext = pred.next; + + // 直接把当前节点的等待状态置为取消,后继节点即便也在cancel可以跨越node节点。 + node.waitStatus = Node.CANCELLED; + + /* + * 如果CAS将tail从node置为pred节点了 + * 则剩下要做的事情就是尝试用CAS将pred节点的next更新为null以彻底切断pred和node的联系。 + * 这样一来就断开了pred与pred的所有后继节点,这些节点由于变得不可达,最终会被回收掉。 + * 由于node没有后继节点,所以这种情况到这里整个cancel就算是处理完毕了。 + * + * 这里的CAS更新pred的next即使失败了也没关系,说明有其它新入队线程或者其它取消线程更新掉了。 + */ + if (node == tail && compareAndSetTail(node, pred)) { + compareAndSetNext(pred, predNext, null); + } else { + // 如果node还有后继节点,这种情况要做的事情是把pred和后继非取消节点拼起来。 + int ws; + if (pred != head && + ((ws = pred.waitStatus) == Node.SIGNAL || + (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && + pred.thread != null) { + Node next = node.next; + /* + * 如果node的后继节点next非取消状态的话,则用CAS尝试把pred的后继置为node的后继节点 + * 这里if条件为false或者CAS失败都没关系,这说明可能有多个线程在取消,总归会有一个能成功的。 + */ + if (next != null && next.waitStatus <= 0) + compareAndSetNext(pred, predNext, next); + } else { + /* + * 这时说明pred == head或者pred状态取消或者pred.thread == null + * 在这些情况下为了保证队列的活跃性,需要去唤醒一次后继线程。 + * 举例来说pred == head完全有可能实际上目前已经没有线程持有锁了, + * 自然就不会有释放锁唤醒后继的动作。如果不唤醒后继,队列就挂掉了。 + * + * 这种情况下看似由于没有更新pred的next的操作,队列中可能会留有一大把的取消节点。 + * 实际上不要紧,因为后继线程唤醒之后会走一次试获取锁的过程, + * 失败的话会走到shouldParkAfterFailedAcquire的逻辑。 + * 那里面的if中有处理前驱节点如果为取消则维护pred/next,踢掉这些取消节点的逻辑。 + */ + unparkSuccessor(node); + } + + /* + * 取消节点的next之所以设置为自己本身而不是null, + * 是为了方便AQS中Condition部分的isOnSyncQueue方法, + * 判断一个原先属于条件队列的节点是否转移到了同步队列。 + * + * 因为同步队列中会用到节点的next域,取消节点的next也有值的话, + * 可以断言next域有值的节点一定在同步队列上。 + * + * 在GC层面,和设置为null具有相同的效果。 + */ + node.next = node; + } + } + + /** + * 唤醒后继线程。 + */ + private void unparkSuccessor(Node node) { + int ws = node.waitStatus; + // 尝试将node的等待状态置为0,这样的话,后继争用线程可以有机会再尝试获取一次锁。 + if (ws < 0) + compareAndSetWaitStatus(node, ws, 0); + + Node s = node.next; + /* + * 这里的逻辑就是如果node.next存在并且状态不为取消,则直接唤醒s即可 + * 否则需要从tail开始向前找到node之后最近的非取消节点。 + * + * 这里为什么要从tail开始向前查找也是值得琢磨的: + * 如果读到s == null,不代表node就为tail,参考addWaiter以及enq函数中的我的注释。 + * 不妨考虑到如下场景: + * 1. node某时刻为tail + * 2. 有新线程通过addWaiter中的if分支或者enq方法添加自己 + * 3. compareAndSetTail成功 + * 4. 此时这里的Node s = node.next读出来s == null,但事实上node已经不是tail,它有后继了! + */ + 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 + public final boolean release(int arg) { + if (tryRelease(arg)) { + /* + * 此时的head节点可能有3种情况: + * 1. null (AQS的head延迟初始化+无竞争的情况) + * 2. 当前线程在获取锁时new出来的节点通过setHead设置的 + * 3. 由于通过tryRelease已经完全释放掉了独占锁,有新的节点在acquireQueued中获取到了独占锁,并设置了head + + * 第三种情况可以再分为两种情况: + * (一)时刻1:线程A通过acquireQueued,持锁成功,set了head + * 时刻2:线程B通过tryAcquire试图获取独占锁失败失败,进入acquiredQueued + * 时刻3:线程A通过tryRelease释放了独占锁 + * 时刻4:线程B通过acquireQueued中的tryAcquire获取到了独占锁并调用setHead + * 时刻5:线程A读到了此时的head实际上是线程B对应的node + * (二)时刻1:线程A通过tryAcquire直接持锁成功,head为null + * 时刻2:线程B通过tryAcquire试图获取独占锁失败失败,入队过程中初始化了head,进入acquiredQueued + * 时刻3:线程A通过tryRelease释放了独占锁,此时线程B还未开始tryAcquire + * 时刻4:线程A读到了此时的head实际上是线程B初始化出来的傀儡head + */ + Node h = head; + // head节点状态不会是CANCELLED,所以这里h.waitStatus != 0相当于h.waitStatus < 0 + if (h != null && h.waitStatus != 0) + // 唤醒后继线程,此函数在acquire中已经分析过,不再列举说明 + unparkSuccessor(h); + return true; + } + return false; + } +``` + +## 获取共享锁的实现 + ```java + public final void acquireShared(int arg) { + 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); + } + } + + /** + * 这个函数做的事情有两件: + * 1. 在获取共享锁成功后,设置head节点 + * 2. 根据调用tryAcquireShared返回的状态以及节点本身的等待状态来判断是否要需要唤醒后继线程。 + */ + private void setHeadAndPropagate(Node node, int propagate) { + // 把当前的head封闭在方法栈上,用以下面的条件检查。 + Node h = head; + setHead(node); + /* + * propagate是tryAcquireShared的返回值,这是决定是否传播唤醒的依据之一。 + * h.waitStatus为SIGNAL或者PROPAGATE时也根据node的下一个节点共享来决定是否传播唤醒, + * 这里为什么不能只用propagate > 0来决定是否可以传播在本文下面的思考问题中有相关讲述。 + */ + if (propagate > 0 || h == null || h.waitStatus < 0 || + (h = head) == null || h.waitStatus < 0) { + Node s = node.next; + if (s == null || s.isShared()) + doReleaseShared(); + } + } + + /** + * 这是共享锁中的核心唤醒函数,主要做的事情就是唤醒下一个线程或者设置传播状态。 + * 后继线程被唤醒后,会尝试获取共享锁,如果成功之后,则又会调用setHeadAndPropagate,将唤醒传播下去。 + * 这个函数的作用是保障在acquire和release存在竞争的情况下,保证队列中处于等待状态的节点能够有办法被唤醒。 + */ + private void doReleaseShared() { + /* + * 以下的循环做的事情就是,在队列存在后继线程的情况下,唤醒后继线程; + * 或者由于多线程同时释放共享锁由于处在中间过程,读到head节点等待状态为0的情况下, + * 虽然不能unparkSuccessor,但为了保证唤醒能够正确稳固传递下去,设置节点状态为PROPAGATE。 + * 这样的话获取锁的线程在执行setHeadAndPropagate时可以读到PROPAGATE,从而由获取锁的线程去释放后继等待线程。 + */ + for (;;) { + Node h = head; + // 如果队列中存在后继线程。 + if (h != null && h != tail) { + int ws = h.waitStatus; + if (ws == Node.SIGNAL) { + if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) + continue; + unparkSuccessor(h); + } + // 如果h节点的状态为0,需要设置为PROPAGATE用以保证唤醒的传播。 + else if (ws == 0 && + !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) + continue; + } + // 检查h是否仍然是head,如果不是的话需要再进行循环。 + if (h == head) + break; + } + } +``` + +## 释放共享锁的实现 +```java + public final boolean releaseShared(int arg) { + if (tryReleaseShared(arg)) { + // doReleaseShared的实现上面获取共享锁已经介绍 + doReleaseShared(); + return true; + } + return false; + } +``` \ No newline at end of file diff --git "a/week_03/32/JAVA\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/week_03/32/JAVA\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..eef69ced892887f698006fa852a9e538ac71afad --- /dev/null +++ "b/week_03/32/JAVA\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,42 @@ +## JAVA内存模型 +### 工作内存: + 工作内存保存了该现场使用的变量的主内存副本拷贝,工作内存是不共享的,只能属于它的线程可以访问。 +### 主内存: + 存放共享的数据,所有的线程都可以访问。 +### 数据存储类型以及操作方式: + 方法中的基本类型本地变量将直接存储在本地内存; + 引用类型的本地变量:本地内存存放变量的引用,变量的实际存储在主内存; + 成员变量,静态变量,类信息都被存储在主内存中; + 主内存共享的方式是线程各拷贝一份数据到本地内存中,操作完成后刷新数据到主内存中; +### 内存间的交互操作: + 8种操作: + Lock(锁定):作用于主内存中的变量,把一个变量标识为一条线程独占的状态。 + Read(读取):作用于主内存中的变量,把一个变量的值从主内存传输到线程的工作内存中。 + Load(载入):作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。 + Use(使用): 作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎。 + Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。 + Store(存储):作用于工作内存中的变量,把工作内存中的一个变量的值传送到主内存中。 + Write(写入):作用于主内存中的变量,把Store操作冲个哦功能在内存中得到的变量的值放入主内存的变量中。 + Unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,之后可以被其它线程锁定。 + 操作规定: + 1:不允许read和load,store和write操作之一单独出现。即不允许一个变量冲主内存被读取了,但是工作内存不接受,或者从工作内存回写了但是主内存不接受 + 2:不允许一个现场丢弃它最近的一个assign操作,即变量在工作内存被更改后必须同步更改回主内存 + 3:在内存种的变量在没有执行过assign操作时,不允许无意义的同步回主内存 + 4:在执行user前必须执行load,在执行store前必须已执行assign + 5:一个变量在同一时刻值允许一个线程对其执行lock操作,一个线程可以对同一个变量执行多次lock,但必须执行相同次数的unlock操作才可以解锁 + 6:一个线程在lock一个变量的时候,将会清空工作内存种此变量的值,执行引擎在use前必须重新read和load + 7:线程不允许unlock其他线程的lock操作,并且unlock操作必须是在本地现场的lock操作之后 + 8:在执行unlock之前,必须首先执行了store和write操作 + +### 内存模型的三大特性 + 1.原子性:read,load,use,assign,store,write,lock,unlock操作具有原子性,但是当内存间的交互是多个操作组成,在多线程情况下一个线程修改了变量但是 + 还没有回写到主内存中时,其他线程读取变量时,变量未lock的话,其他线程读取的变量的值还是旧值 + 2.可见性:可见性指当一个现场修改了共享变量的值,其他现场能够理解得知这个修改。java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从 + 主内存刷新新变量值来实现可见性的。 + 主要有三种实现可见性的方式: + 1.volatile:会强制将该变量自己和当时其他变量的状态都刷出缓存。 + 2.synchronized:对一个变量执行unlock操作之前,必须把变量值同步回主内存 + 3.final:被final修饰的字段在构造器中初始化完成,并且没有发生this逃逸(其他线程通过this引用访问到初始化一半的对象),那么其他线程就能看见final字段的值 + 3.有序性:有序性指在本现场内观察,所有操作都是有序的。在一个现场观察另一个现场,所有操作都是无序的,无序是因为发生了指令重排序。在JAVA内存模型中,允许编译器和 + 处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。volatile关键字通过添加内存屏障的方式来禁止指令重排。也可以 + 通过synchronized来保证有序性,它保证每个时刻只有一个线程执行同步的代码,相当于让线程顺序执行同步代码 \ No newline at end of file diff --git a/week_03/32/ReentrantLock.md b/week_03/32/ReentrantLock.md new file mode 100644 index 0000000000000000000000000000000000000000..6556f8308bc33653f3151624c10727f7957f5e75 --- /dev/null +++ b/week_03/32/ReentrantLock.md @@ -0,0 +1,293 @@ +## ReentrantLock源码阅读 + +## 1.1 ReentrantLock: + 1.1.1: 由内部静态抽象类sync和NonfairSync,FairSync来实现lock接口。 + +## 1.2 Sync: + 1.2.1:Sync继承AbstractQueuedSynchronizer; + 1.2.3:Sync方法: + 1.2.3.1: +```java + abstract void lock(); //抽象方法加锁,由ReentrantLock的内部类NonfairSync,FairSync来实现 +``` + 1.2.3.2: +```java + final boolean nonfairTryAcquire(int acquires) { //非公平式获取锁,并判断是否能更改同步状态和上锁 + final Thread current = Thread.currentThread(); //获取当前线程 + int c = getState(); //获取同步状态 + if (c == 0) { //判断锁有没有被占用 0:未占用 + if (compareAndSetState(0, acquires)) { //设置同步状态 + setExclusiveOwnerThread(current); //设置线程current为独占锁的拥有者 + return true; + } + } + else if (current == getExclusiveOwnerThread()) { //如果当前线程是独占锁的拥有者 + int nextc = c + acquires; //同步状态叠加 + if (nextc < 0) // overflow + throw new Error("Maximum lock count exceeded"); + setState(nextc); //设置同步状态 + return true; + } + return false; + } +``` + 1.2.3.3: +```java + protected final boolean tryRelease(int releases) { //设置同步状态state,当state为0时释放锁 + int c = getState() - releases; //同步状态减去releases + if (Thread.currentThread() != getExclusiveOwnerThread()) //判断当前线程是否锁的拥有者 + throw new IllegalMonitorStateException(); + boolean free = false; + if (c == 0) { //如果为0表示当前线程释放了锁,此时这个锁可以被其他线程占用 + free = true; + setExclusiveOwnerThread(null); + } + setState(c); //更新同步状态 + return free; + } +``` + 1.2.3.4: +```java + protected final boolean isHeldExclusively() { //判断当前线程是否锁的拥有者 + return getExclusiveOwnerThread() == Thread.currentThread(); + } +``` + 1.2.3.5: +```java + final ConditionObject newCondition() { //创建一个队列 + return new ConditionObject(); + } +``` 1.2.3.6: +```java + final Thread getOwner() { //获取独占锁的拥有线程 + return getState() == 0 ? null : getExclusiveOwnerThread(); //如果state为0说明没有被上锁 + } +``` + 1.2.3.7: +```java + final int getHoldCount() { //如果是当前线程占用锁 返回同步状态值,不是返回0 + return isHeldExclusively() ? getState() : 0; + } +``` + 1.2.3.8: +```java + final boolean isLocked() { //判断是否被上锁 + return getState() != 0; + } +``` + 1.2.3.9: +```java + private void readObject(java.io.ObjectInputStream s) //反序列化 + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + setState(0); // reset to unlocked state + } +``` +## 1.3 ReentrantLock构造方法: + 1.3.1: +```java + public ReentrantLock() { //默认选择非公平锁 + sync = new NonfairSync(); + } +``` + 1.3.2: +```java + public ReentrantLock(boolean fair) { //当fair为true时选择公平锁,否则选择非公平锁 + sync = fair ? new FairSync() : new NonfairSync(); + } +``` +## 1.4 ReentrantLock方法: + 1.4.1: +```java + public void lock() { //上锁,最后实现是看选择了那个内部类(FairSync,NonfairSync)的实现方式 + sync.lock(); + } +``` + 1.4.2: +```java + //尝试获取锁,如果已经被上锁了,该锁是当前线程拥有则同步状态+1,如果该锁不是当前线程拥有则排队等候获取锁 + public void lockInterruptibly() throws InterruptedException { + sync.acquireInterruptibly(1); + } +``` + 1.4.3: +```java + public boolean tryLock() { //尝试获取锁,和1.4.2方法不同在于如果已经被上锁并且锁的拥有者不是当前线程的话就不排队 + return sync.nonfairTryAcquire(1); + } +``` + 1.4.4: +```java + public boolean tryLock(long timeout, TimeUnit unit) //尝试获取锁,和1.4.2方法不同在于如果排队,设置排队超时的时间 + throws InterruptedException { + return sync.tryAcquireNanos(1, unit.toNanos(timeout)); + } +``` + 1.4.5: +```java + //释放锁,尝试更新同步状态,当锁的拥有者不是当前线程时抛错IllegalMonitorStateException,当前线程时锁的拥有者时候更新同步状态,同步状态为0则释放锁 + public void unlock() { + sync.release(1); + } +``` + 1.4.6: +```java + public Condition newCondition() { //创建一个队列 + return sync.newCondition(); + } +``` + 1.4.7: +```java + public int getHoldCount() { //如果是当前线程占用锁 返回同步状态值,不是返回0 + return sync.getHoldCount(); + } +``` + 1.4.8: +```java + public boolean isHeldByCurrentThread() { //判断当前线程是否锁的拥有者 + return sync.isHeldExclusively(); + } +``` + 1.4.9: +```java + public boolean isLocked() { //判断同步状态是否为0 + return sync.isLocked(); + } +``` + 1.4.10: +```java + public final boolean isFair() { //判断是否创建的公平锁 + return sync instanceof FairSync; + } +``` + 1.4.11: +```java + protected Thread getOwner() { //获取锁的拥有线程 + return sync.getOwner(); + } +``` + 1.4.12: +```java + public final boolean hasQueuedThreads() { //是否有线程正在等待获取锁 + return sync.hasQueuedThreads(); + } +``` + 1.4.13: +```java + public final boolean hasQueuedThread(Thread thread) { //判断线程thread是否在等待锁的队列中 + return sync.isQueued(thread); + } +``` + 1.4.14: +```java + public final int getQueueLength() { //获取有多少线程在排队等待获取锁 + return sync.getQueueLength(); + } +``` + 1.4.15: +```java + protected Collection getQueuedThreads() { //获取等待获取锁的队列 + return sync.getQueuedThreads(); + } +``` + 1.4.16: +```java + public boolean hasWaiters(Condition condition) { //判断是否有等待节点 + if (condition == null) + throw new NullPointerException(); + if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) + throw new IllegalArgumentException("not owner"); + return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); + } +``` + 1.4.17: +```java + public int getWaitQueueLength(Condition condition) { //获取等待队列的长度 + if (condition == null) + throw new NullPointerException(); + if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) + throw new IllegalArgumentException("not owner"); + return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); + } + +``` + 1.4.18: +```java + protected Collection getWaitingThreads(Condition condition) { //获取所有等待线程 + if (condition == null) + throw new NullPointerException(); + if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) + throw new IllegalArgumentException("not owner"); + return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); + } +``` +## 1.5 NonfairSync ---非公平锁 + 1.5.1:NonfairSync继承Sync; + 1.5.2:NonfairSync方法: + 1.5.2.1: +```java + final void lock() { //上锁,上锁失败才加入获取锁的队列 + if (compareAndSetState(0, 1)) //原子操作,变更同步状态值 + setExclusiveOwnerThread(Thread.currentThread()); //变更同步状态成功,设置锁的拥有者为当前线程 + else + acquire(1);//变更失败表示锁的拥有者不是当前线程,加入队列等到获取锁 + } +``` + 1.5.2.2: +```java + protected final boolean tryAcquire(int acquires) { //非公平式获取锁,并判断是否能更改同步状态和上锁 + return nonfairTryAcquire(acquires); + } +``` +## 1.6 FairSync ---公平锁 + 1.6.1:FairSync继承Sync; + 1.6.2:FairSync方法: + 1.6.2.1: +```java + final void lock() { //上锁,先尝试获取锁,获取失败加入队列等待 + acquire(1); + } +``` + 1.6.2.2: +```java + protected final boolean tryAcquire(int acquires) { //尝试上锁 + final Thread current = Thread.currentThread(); //获取当前线程 + int c = getState(); //获取锁的同步状态 + if (c == 0) { //判断同步状态是否为0 + if (!hasQueuedPredecessors() && //判断当前线程是否可以获取锁,等待获取锁的队列为空或者在队列第一位 + compareAndSetState(0, acquires)) { //原子操作,变更同步状态值 + setExclusiveOwnerThread(current); //变更同步状态成功设置当前线程为锁的拥有者 + return true; + } + } + else if (current == getExclusiveOwnerThread()) { //判断当前线程是否锁的拥有者 + int nextc = c + acquires; + if (nextc < 0) + throw new Error("Maximum lock count exceeded"); + setState(nextc); //变更同步状态值 + return true; + } + return false; + } +``` + +## 1.7:ReentrantLock总结: + ReentrantLock是可重入的独占锁。比起synchronized功能更加丰富, + ReentrantLock 提供了包括定时的锁等待、可中断的锁等待、公平性以及实现非块结构的加锁 + Condition 对线程的等待和唤醒等操作更加灵活,一个 ReentrantLock 可以有多个 Condition 实例,更有扩展性 + ReentrantLock 需要显示的获取锁,并在 finally 中释放锁。 + + + + + + + + + + + + + + + diff --git a/week_03/32/Semaphore.md b/week_03/32/Semaphore.md new file mode 100644 index 0000000000000000000000000000000000000000..49cd7a5ec61f72e195a16a125119b625b6289efe --- /dev/null +++ b/week_03/32/Semaphore.md @@ -0,0 +1,176 @@ +## Semaphore源码学习 + +## 1.1 Semaphore: + 1.1.1:实现了java.io.Serializable,说明可以进行序列化。 +## 1.2 Sync: + 1.2.1:Sync继承AbstractQueuedSynchronizer; + 1.2.2:Sync方法: + 1.2.2.1: +```java + Sync(int permits) { //构造函数设置状态数 + setState(permits); + } +``` + 1.2.2.2: +```java + final int getPermits() { //获取许可 + return getState(); + } +``` + 1.2.2.3: +```java + final int nonfairTryAcquireShared(int acquires) { //非公平策略获取 + for (;;) { + int available = getState(); //获取许可 + int remaining = available - acquires; //剩余许可 + if (remaining < 0 || + compareAndSetState(available, remaining)) //许可小于0或者原子操作设置许可值 + return remaining; + } + } +``` + 1.2.2.4: +```java + protected final boolean tryReleaseShared(int releases) { //释放许可 + for (;;) { + int current = getState(); //获取许可 + int next = current + releases; //可用许可 + if (next < current) // overflow //释放时,释放后的许可肯定比释放前多 + throw new Error("Maximum permit count exceeded"); + if (compareAndSetState(current, next)) //cas操作修改可用许可数量 + return true; + } + } +``` + 1.2.2.5: +```java + final void reducePermits(int reductions) { //减少reductions个可用许可数量 + for (;;) { + int current = getState(); //获取剩余许可数量 + int next = current - reductions; //减少后的可用许可数量 + if (next > current) // underflow // + throw new Error("Permit count underflow"); + if (compareAndSetState(current, next)) + return; + } + } +``` + 1.2.2.6: +```java + final int drainPermits() { //获取剩余可用的许可数量 + for (;;) { + int current = getState(); + if (current == 0 || compareAndSetState(current, 0)) + return current; + } + } +``` +## 1.3 NonfairSync: + 1.3.1:NonfairSync类继承了Sync类,表示采用非公平策略获取资源,其只有一个tryAcquireShared方法,重写了AQS的该方法 + 1.3.2:NonfairSync方法 + 1.3.2.1: +```java + NonfairSync(int permits) { //初始化构造一个许可数量 + super(permits); + } +``` + 1.3.2.1: +```java + protected int tryAcquireShared(int acquires) { //非公平获取一个许可 + return nonfairTryAcquireShared(acquires); + } +``` + +## 1.4 FairSync: + 1.4.1:FairSync类继承了Sync类,表示采用公平策略获取资源,其只有一个tryAcquireShared方法,重写了AQS的该方法 + 1.4.2:FairSync方法: + 1.4.2.1: +```java + FairSync(int permits) { //初始化构造一个许可数量 + super(permits); + } +``` + 1.4.2.2: +```java + protected int tryAcquireShared(int acquires) { + for (;;) { + if (hasQueuedPredecessors()) //首先查看队列中是否在排队等候获取许可,如果没有队列或者队列下一个能获取许可的线程时当前线程 + return -1; + int available = getState(); //获取许可 + int remaining = available - acquires; //剩余许可 + if (remaining < 0 || + compareAndSetState(available, remaining)) //cas操作修改可用许可数量 + return remaining; + } + } +``` + +## 1.5:Semaphore构造器: + 1.5.1: +```java + public Semaphore(int permits) { //创建一个permits许可数量的非公平策略获取资源 + sync = new NonfairSync(permits); + } +``` + 1.5.2: +```java + public Semaphore(int permits, boolean fair) { //创建一个permits许可数量并根据fair来选择由什么策略获取支援 + sync = fair ? new FairSync(permits) : new NonfairSync(permits); + } +``` +## 1.6:Semaphore方法: + 1.6.1: +```java + public void acquire() throws InterruptedException { //调用 acquireSharedInterruptibly 响应中断的方式获取许可 + sync.acquireSharedInterruptibly(1); + } +``` + 1.6.2: +```java + public void acquireUninterruptibly() { //非响应中断的方式获取许可 + sync.acquireShared(1); + } +``` + 1.6.3: +```java + public boolean tryAcquire() { //尝试获取许可 + return sync.nonfairTryAcquireShared(1) >= 0; + } +``` + 1.6.4: +```java + public boolean tryAcquire(long timeout, TimeUnit unit) + throws InterruptedException { //尝试获取许可,设置超时时间 + return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); + } +``` + 1.6.5: +```java + public void release() { //释放许可 + sync.releaseShared(1); + } +``` + 1.6.6 +```java + public int drainPermits() { //获取剩余可用的许可数量 + return sync.drainPermits(); + } +``` + +## Semaphore总结: + 1.Semaphore用state变量来作为许可剩余数量,给线程提供当前资源是否可以获取 + 2.Semaphore也支持公平和非公平策略 + 3.Semaphore也提供了排队等候的机制 + 4.Semaphore可用做连接池, + + + + + + + + + + + + diff --git a/week_03/32/synchronized.md b/week_03/32/synchronized.md new file mode 100644 index 0000000000000000000000000000000000000000..dc196921597916cd4f3ae187ebc179381107b498 --- /dev/null +++ b/week_03/32/synchronized.md @@ -0,0 +1,14 @@ +## synchronized学习 +### synchronized的作用: + 1.原子性:确保现场互斥的访问同步代码; + 2.可见性:保证共享变量的修改能够及时可见,在JAVA内存模型中操作规定:对一个变量unlock操作之前,必须首先执行了store和write操作; + 一个线程在lock一个变量的时候,将会清空工作内存种此变量的值,执行引擎在use前必须重新read和load + 3.有序性:有效解决重排序问题,即一个unlock操作先行发生于后面对同一个锁的lock操作 +### synchronized的用法: + 1.当synchronized修饰方法的时候,锁对象实例 + 2.当synchronized修饰静态方法的时候,锁的是类对象 + 3.当synchronized(对象M)时,锁的是M实例 +### synchronized原理: + 每个对象都有一个monitor监视器,调用monitorenter就是尝试获取这个对象,成功获取到了就将值+1,离开就将值-1,如果是线程重如,再将值+1 + 说明monitor对象是支持可重入的。 + \ No newline at end of file diff --git a/week_03/32/volatile.md b/week_03/32/volatile.md new file mode 100644 index 0000000000000000000000000000000000000000..860db55e855ddd762430ed0c8d07ad356bca8032 --- /dev/null +++ b/week_03/32/volatile.md @@ -0,0 +1,12 @@ +## volatile学习 +### volatile原理: + volatile是一种弱同步机制,用来确保将变量的更新操作通知到其他线程。当把变量申明为volatile类型后,编译器与运行时都会注意到这个变量是共享的, + 因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量的 + 时总会返回最新写入的值。 + 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。 + 当变量被申明为volatile时,JVM保证每次读取变量都从内存中读,不从CPU缓存中获取。 +### volatile特性: + 1.保证被volatile修饰的变量的可见性,当一个线程修改了这个变量的值,volatile保证了新值能马上同步到主内存,然后每次使用前才从主内存获取。 + 2.有volatile修饰的变量禁止指令重排序优化。 +### volatile性能: + volatile的读性能和普通变量差不多,写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。 diff --git "a/week_03/32/\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/week_03/32/\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..dfc4341f2ff8e42380440198585b1c0f42f850cb --- /dev/null +++ "b/week_03/32/\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,24 @@ +## 分布式锁 +### 分布式锁目的: + 控制分布式系统之间同步访问共享资源的一种方式。再分布式系统中,常常需要协调他们的动作。如果不同的系统或者是同一个系统的 + 不同主机之间共享了一个或者一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性。 +### 分布式锁的理解: + 将分布式系统之间的共享资源由一个统一的服务来管理。也就是说将多个系统之间的主内存中的共享变量的存储位置由原来的各 + 系统的主内存变成了统一管理服务(zookeeper,redis...)的主内存中,由这些服务器提供接口供分布式系统调用给共享变量上锁, + 释放锁,更新共享变量等操作。 +### 分布式锁的实现: + 1.获取锁: + 提供一种方式,多个客户端并发操作,只能有一个客户端能满足相应的要求。 + 如数据库的for update的sql语句,或者插入一个含有唯一约束的数据等 + 如redis的setnx等 + 如ZooKeeper的秋最小节点的方式 + 这些都可以保证只有一个客户端获取到锁 + 2.释放锁: + 1.正常释放锁 + 2.释放锁的操作没有执行时,怎么释放锁,(持有锁的客户端挂了,发出释放锁命令失败等) + 如redis正常情况下释放锁是删除lock_key,异常情况下,判断lock_key的超时时间 + 如ZooKeeper正常情况下释放锁是删除临时节点,异常情况下,服务器也会主动删除临时节点 + 3.怎么知道锁被释放了 + 1.没有获取锁的客户端轮训尝试获取锁 + 2.服务端通知客户端锁被释放了 + \ No newline at end of file