From 9e54ab8cfc2c64bf05b162b7d237b845940d85a2 Mon Sep 17 00:00:00 2001 From: Apollo2016926 <944644308@qq.com> Date: Fri, 27 Dec 2019 14:35:03 +0800 Subject: [PATCH 1/2] ss --- week_03/61/AbstractQueuedSynchronizer.md | 131 +++++ week_03/61/ReentrantLock.md | 546 ++++++++++++++++++ week_03/61/Semaphore.md | 183 ++++++ ...05\345\255\230\346\250\241\345\236\213.md" | 52 ++ week_03/61/synchronized.md | 45 ++ week_03/61/volatile.md | 29 + ...0\351\227\264\346\223\215\344\275\234.png" | Bin 0 -> 7168 bytes ...06\345\270\203\345\274\217\351\224\201.md" | 41 ++ 8 files changed, 1027 insertions(+) create mode 100644 week_03/61/AbstractQueuedSynchronizer.md create mode 100644 week_03/61/ReentrantLock.md create mode 100644 week_03/61/Semaphore.md create mode 100644 "week_03/61/java\345\206\205\345\255\230\346\250\241\345\236\213.md" create mode 100644 week_03/61/synchronized.md create mode 100644 week_03/61/volatile.md create mode 100644 "week_03/61/\345\206\205\345\255\230\351\227\264\346\223\215\344\275\234.png" create mode 100644 "week_03/61/\345\210\206\345\270\203\345\274\217\351\224\201.md" diff --git a/week_03/61/AbstractQueuedSynchronizer.md b/week_03/61/AbstractQueuedSynchronizer.md new file mode 100644 index 0000000..b09e8aa --- /dev/null +++ b/week_03/61/AbstractQueuedSynchronizer.md @@ -0,0 +1,131 @@ +#
pu&{6oNF@fDDKL+GwL?vI7oit2>>5T?s3oB*~M2Wr0TUuQeXn#IF4R z)ZoAg;E2}JVo<`b$L=r+_n#33{(;md4;ne`+rYp%Fc|!;ukWmcghXcSxt%I+oDwXz z!|2ae29zSGs;V@VO5LQV1_lPM>@5Sj8gQ7+kt^Kk{`Eb-P<1$#g$~RTC>uK(WNo|B zGO8NZtkYq>l%Ae!xm;jn)^{j=` zV(`uicBG$xzBeE0sxU;4+3CAZeDyurZ!!Y@?$98HTmDmI1)D+4-!SFF#%vT62*j{>|s zBG9JDcNQqu2kll=-<(SVXnxE3)ZPoP4FhZgvkt=2)V95)riWKE;A8WA(?KgAGDg!o z_&i1W;8~Yw9wx(BhgrZWYQ2pLwk*3FR+75~E_rt?M-+Q!=CyWK3}ohx51T29(Q9gZ zWk00HB7l34ZXc@7M{W=*Ft$0PFPxMBoKhXnXDmT!DYQsSd)pA^oA~XArqTZH4I9?H zOHqAM%0#Ni*6qwod?jvlQHB;iw;4OjHBcyx3v )5T%4#=yhAU@81^rjh3O-*%) z-ntK51;=ZGzr>w&qBdu_vy9(xBj{MpI(kii6W8X7O_k> i7<)EY3&C3*R`^GPPqd`&OBzUL8&RC&}r3BSe
mm$tfSX; z01up&S*Wr2K|LryTVqX|My@sUw^#L#OOJ}hdwjlfpD&=u$3ws~XEh}cw%gp>B6EzNjld0ld%Tz1j+AoAp-pzE+#`t)Om|rtixf@KkdkHts-@XU zaJ2dAu(wgLQIXqm84F3J(V&t6?F2TM_lKLtVguji_LROzK*Q`gA^>ka9u0Ilva_*_ zjcpOP8HBdhL tR7`l!!Mbo( AV&<%gUkold%Mvs!zcjTcJAsm)dBOp? n3p*xy7m`dA+G=6_P#m;U^qADKFw>IS0UdA zt$iBpsp(lLZj+^y`_`g& $WvNM-4(#Kf?7M $ay0T#w+4YTgG%xanOdB z%GPWNV+J2(D%vCWppv;d#3+&yV4K)XU$?j_XA?l}UZlEL84!-)L7IR+7}JYN#6=#U zcq9OT2eTJ7UEcWtBmITeo-6^7!VsTb1*3*No2P~hx;^YZFSSA@lxjAfq47jh>wxTh zce%RuQ~y2a`9A^2N69OmO1!Si+0^b!V#DUgd0U #@9RrV?+<6W3r_2X{6d;rm$Ur1)^Q25ymT4QnRqL~iVr%j|nk z#{{{Vg|l 7 分布式锁 + +### 简介 + +为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题! + +### 什么是分布式锁 + +- 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。 +- 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。(我觉得分布式情况下之所以问题变得复杂,主要就是需要考虑到网络的延时和不可靠。。。一个大坑) +- 分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。。 + +### 分布式锁应具备的条件 + ++ 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行 + ++ 高可用的获取锁与释放锁 + ++ 高性能的获取锁与释放锁 + ++ 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误) + ++ 具备锁失效机制,防止死锁 + ++ 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失 + + ### 应用方式 + ++ [基于数据库实现分布式锁](https://mp.weixin.qq.com/s?__biz=Mzg2ODA0ODM0Nw==&mid=2247483982&idx=1&sn=305e22c0e74a028cb9180ac03bd110fa&scene=21#wechat_redirect); + ++ [基于缓存(Redis等)实现分布式锁](https://mp.weixin.qq.com/s?__biz=Mzg2ODA0ODM0Nw==&mid=2247483993&idx=1&sn=f6b408b01e10a2ab75256acf07e08537&scene=21#wechat_redirect); + ++ [基于Zookeeper实现分布式锁](https://mp.weixin.qq.com/s?__biz=Mzg2ODA0ODM0Nw==&mid=2247483988&idx=1&sn=9e29b4aa64becd7d7764062dc5389eed&scene=21#wechat_redirect); + + + + + + + + \ No newline at end of file -- Gitee From d28fea25943e82918663c9eb68613c7a87e04998 Mon Sep 17 00:00:00 2001 From: Apollo2016926 <944644308@qq.com> Date: Fri, 27 Dec 2019 14:35:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E7=AC=AC=E4=B8=89=E5=91=A8=E8=BE=93?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week_03/61/AbstractQueuedSynchronizer.md | 131 +++++ week_03/61/ReentrantLock.md | 546 ++++++++++++++++++ week_03/61/Semaphore.md | 183 ++++++ ...05\345\255\230\346\250\241\345\236\213.md" | 52 ++ week_03/61/synchronized.md | 45 ++ week_03/61/volatile.md | 29 + ...0\351\227\264\346\223\215\344\275\234.png" | Bin 0 -> 7168 bytes ...06\345\270\203\345\274\217\351\224\201.md" | 41 ++ 8 files changed, 1027 insertions(+) create mode 100644 week_03/61/AbstractQueuedSynchronizer.md create mode 100644 week_03/61/ReentrantLock.md create mode 100644 week_03/61/Semaphore.md create mode 100644 "week_03/61/java\345\206\205\345\255\230\346\250\241\345\236\213.md" create mode 100644 week_03/61/synchronized.md create mode 100644 week_03/61/volatile.md create mode 100644 "week_03/61/\345\206\205\345\255\230\351\227\264\346\223\215\344\275\234.png" create mode 100644 "week_03/61/\345\210\206\345\270\203\345\274\217\351\224\201.md" diff --git a/week_03/61/AbstractQueuedSynchronizer.md b/week_03/61/AbstractQueuedSynchronizer.md new file mode 100644 index 0000000..b09e8aa --- /dev/null +++ b/week_03/61/AbstractQueuedSynchronizer.md @@ -0,0 +1,131 @@ +# AbstractQueuedSynchronizer + +### 简介 + +AQS的主要使用方式是继承它作为一个内部辅助类实现同步原语,它可以简化你的并发工具的内部实现,屏蔽同步状态管理、线程的排队、等待与唤醒等底层操作。 +AQS设计基于模板方法模式,开发者需要继承同步器并且重写指定的方法,将其组合在并发组件的实现中,调用同步器的模板方法,模板方法会调用使用者重写的方法。 + +### 源码解析 + +#### 主要内部类 + +```java +static final class Node { + //共享模式的标记 + static final Node SHARED = new Node(); + //标识一个节点是独占模式 + static final Node EXCLUSIVE = null; + // waitStatus变量的值,标志着线程被取消 + static final int CANCELLED = 1; + // waitStatus变量的值,标志着后继线程(即队列中此节点之后的节点)需要被阻塞.(用于独占锁) + static final int SIGNAL = -1; +// waitStatus变量的值,标志着线程在Condition条件上等待阻塞.(用于Condition的await等待) + static final int CONDITION = -2; + // waitStatus变量的值,标志着下一个acquireShared方法线程应该被允许。(用于共享锁) + static final int PROPAGATE = -3; + + // 标记着当前节点的状态,默认状态是0, 小于0的状态都是有特殊作用,大于0的状态表示已取消 + volatile int waitStatus; + + // prev和next实现一个双向链表 + volatile Node prev; + volatile Node next; + +// 当前节点保存的线程 + volatile Thread thread; + + // 可能有两种作用:1. 表示下一个在Condition条件上等待的节点 + // 2. 表示是共享模式或者独占模式,注意第一种情况节点一定是共享模式 + Node nextWaiter; + +// 是否是共享模式 + final boolean isShared() { + return nextWaiter == SHARED; + } + //返回前一个节点prev,如果为null,则抛出NullPointerException异常 + final Node predecessor() throws NullPointerException { + Node p = prev; + if (p == null) + throw new NullPointerException(); + else + return p; + } +//// 用于创建链表头head,或者共享模式SHARED + Node() { // Used to establish initial head or SHARED marker + } + +// 把共享模式还是互斥模式存储到nextWaiter这个字段里面了 + Node(Thread thread, Node mode) { // Used by addWaiter + this.nextWaiter = mode; + this.thread = thread; + } + +// 等待的状态,在Condition中使用 + Node(Thread thread, int waitStatus) { // Used by Condition + this.waitStatus = waitStatus; + this.thread = thread; + } +``` + +#### 主要属性 + +```java +//双向链表头、尾 +private transient volatile Node head; +private transient volatile Node tail; +// 控制加锁解锁的状态变量 +private volatile int state; + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long stateOffset;//状态变量state的偏移量 + private static final long headOffset; + private static final long tailOffset; + private static final long waitStatusOffset;// 等待状态的偏移量(Node的属性) + private static final long nextOffset;// 下一个节点的偏移量(Node的属性) + + static { + try { + stateOffset = unsafe.objectFieldOffset + (AbstractQueuedSynchronizer.class.getDeclaredField("state")); + headOffset = unsafe.objectFieldOffset + (AbstractQueuedSynchronizer.class.getDeclaredField("head")); + tailOffset = unsafe.objectFieldOffset + (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); + waitStatusOffset = unsafe.objectFieldOffset + (Node.class.getDeclaredField("waitStatus")); + nextOffset = unsafe.objectFieldOffset + (Node.class.getDeclaredField("next")); + + } catch (Exception ex) { throw new Error(ex); } + } +/** + * 通过CAS函数设置head值,仅仅在enq方法中调用 + */ + private final boolean compareAndSetHead(Node update) { + return unsafe.compareAndSwapObject(this, headOffset, null, update); + } +// 互斥模式下使用:尝试获取锁 + protected boolean tryAcquire(int arg) { + throw new UnsupportedOperationException(); + } + + // 互斥模式下使用:尝试释放锁 + protected boolean tryRelease(int arg) { + throw new UnsupportedOperationException(); + } + +// 共享模式下使用:尝试获取锁 + protected int tryAcquireShared(int arg) { + throw new UnsupportedOperationException(); + } + + // 共享模式下使用:尝试释放锁 + protected boolean tryReleaseShared(int arg) { + throw new UnsupportedOperationException(); + } +// 如果当前线程独占着锁,返回true + protected boolean isHeldExclusively() { + throw new UnsupportedOperationException(); + } + + +``` \ No newline at end of file diff --git a/week_03/61/ReentrantLock.md b/week_03/61/ReentrantLock.md new file mode 100644 index 0000000..3d8d46a --- /dev/null +++ b/week_03/61/ReentrantLock.md @@ -0,0 +1,546 @@ +#ReentrantLock + +### 简介 + +jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。 + +### 源码分析 + +#### 内部类 + +```java +abstract static class Sync extends AbstractQueuedSynchronizer {}//抽象类Sync实现了AQS的部分方法 +static final class FairSync extends Sync {}//FairSync实现了Sync,主要用于公平锁的获取 +static final class NonfairSync extends Sync {}//NonfairSync实现了Sync,主要用于非公平锁的获取; +``` + +#### 主要属性及构造方法 + +```java +//它在构造方法中初始化,决定使用公平锁还是非公平锁的方式获取锁 +private final Sync sync; +// 默认构造方法 非公平锁 + public ReentrantLock() { + sync = new NonfairSync(); + } + + //可选择使用公平锁还是非公平锁 + public ReentrantLock(boolean fair) { + sync = fair ? new FairSync() : new NonfairSync(); + } +``` + +##### lock + ++ 公平锁 + + ReentrantLock reentrantLock = new ReentrantLock(true); + + reentrantLock 为new FairSync()实例 + + ```java + //ReentrantLock.lock() + public void lock() { + sync.lock();/ + } + //ReentrantLock.FairSync.lock() + final void lock() { + acquire(1);//调用AbstractQueuedSynchronizer.acquire获取锁 + } + //AbstractQueuedSynchronizer.acquire + public final void acquire(int arg) { + //尝试获取锁 + if (!tryAcquire(arg) && + //获取锁失败,加入失败队列 + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) + selfInterrupt(); + } + //ReentrantLock.FairSync.tryAcquire() + protected final boolean tryAcquire(int acquires) { + //当前线程 + final Thread current = Thread.currentThread(); + //获取当前状态变量的值 + int c = getState(); + // 如果状态变量的值为0,说明暂时还没有人占有锁 + if (c == 0) { + //h如果没有其他线程在排队 + if (!hasQueuedPredecessors() && + //尝试更新state的值为1 + compareAndSetState(0, acquires)) { + //获取到了锁,将AbstractOwnableSynchronizer.exclusiveOwnerThread设置为当前线程 + setExclusiveOwnerThread(current); + return true; + } + } + //如果当前线程本身占着锁,现在有来获取锁直接让他获取锁并返回 + else if (current == getExclusiveOwnerThread()) { + //状态变量state+1 + int nextc = c + acquires; + if (nextc < 0)// + throw new Error("Maximum lock count exceeded"); + setState(nextc); + return true; + } + return false; + } + //AbstractQueuedSynchronizer#addWaiter + //尝试获取锁失败时调用此方法 + private Node addWaiter(Node mode) { + //新建一个节点 mode为独占模式 addWaiter(Node.EXCLUSIVE) + Node node = new Node(Thread.currentThread(), mode); + //先尝试将新节点加到尾节点后面,成功则返回新节点,失败调用enq()不断尝试 + Node pred = tail; + if (pred != null) { + //新节点的前置节点设置为当前尾节点 + node.prev = pred; + //CAS更新尾节点为新节点 + if (compareAndSetTail(pred, node)) { + pred.next = node; + return node; + } + } + //新节点入队失败调用enq() + enq(node); + return node; + } + //AbstractQueuedSynchronizer.enq() + 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; + // CAS更新尾节点为新节点 + if (compareAndSetTail(t, node)) { + 成功设置旧的尾节点的下一个节点为新节点 + t.next = node; + return t; + } + } + } + } + + //AbstractQueuedSynchronizer.acquireQueued() + //调用上面的addWaiter()方法使得新节点已经成功入队了 + // 这个方法是尝试让当前节点来获取锁的 + final boolean acquireQueued(final Node node, int arg) { + boolean failed = true;//失败标记 + try { + boolean interrupted = false;//中断标记 + //自旋 + for (;;) { + //获取当前节点的前一个节点 + final Node p = node.predecessor(); + //如果当前节点的前一个节点为head节点,则说明轮到自己获取锁了 + // 调用ReentrantLock.FairSync.tryAcquire()方法再次尝试获取锁 + if (p == head && tryAcquire(arg)) { + //获取到锁,将当前节点设置为新的头节点,这里同时只会有一个线程在运行所以不用CAS + setHead(node); + p.next = null; // help GC + failed = false;//未失败 + return interrupted; + } + //是否需要阻塞 + if (shouldParkAfterFailedAcquire(p, node) && + //真正阻塞的方法 + parkAndCheckInterrupt()) + //如果中断了 + interrupted = true; + } + } finally { + //如果失败了 + if (failed) + //取消获取锁 + cancelAcquire(node); + } + } + //.AbstractQueuedSynchronizr#shouldParkAfterFailedAcquire() + // 这个方法是在上面的for()循环里面调用的 + // 第一次调用会把前一个节点的等待状态设置为SIGNAL,并返回false + // 第二次调用才会返回true + private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { + //上一节点的等待状态 + // 注意Node的waitStatus字段我们在上面创建Node的时候并没有指定 ,所以为0 + int ws = pred.waitStatus; + // 如果等待状态为SIGNAL(等待唤醒),直接返回true + if (ws == Node.SIGNAL) + return true; + // 如果前一个节点的状态大于0,也就是已取消状态 + if (ws > 0) { + // 把前面所有取消状态的节点都从链表中删除 + do { + node.prev = pred = pred.prev; + } while (pred.waitStatus > 0); + pred.next = node; + } else { + /* + 如过前一个节点的状态小于等于0,则它状态设置为等待唤醒 + 这里可以简单地理解为把初始状态0设置为SIGNAL + CONDITION是条件锁的时候使用的 + PROPAGATE是共享锁使用的 + */ + compareAndSetWaitStatus(pred, ws, Node.SIGNAL); + } + return false; + } + // AbstractQueuedSynchronizer.parkAndCheckInterrupt 阻塞 + private final boolean parkAndCheckInterrupt() { + // 阻塞当前线程 + // 底层调用的是Unsafe的park()方法 + LockSupport.park(this); + // 返回是否已中断 + return Thread.interrupted(); + } + ``` + +获取锁的主要过程大致如下: + +(1)尝试获取锁,如果获取到了就直接返回了; + +(2)尝试获取锁失败,再调用addWaiter()构建新节点并把新节点入队; + +(3)然后调用acquireQueued()再次尝试获取锁,如果成功了,直接返回; + +(4)如果再次失败,再调用shouldParkAfterFailedAcquire()将节点的等待状态置为等待唤醒(SIGNAL); + +(5)调用parkAndCheckInterrupt()阻塞当前线程; + +(6)如果被唤醒了,会继续在acquireQueued()的for()循环再次尝试获取锁,如果成功了就返回; + +(7)如果不成功,再次阻塞,重复(3)(4)(5)直到成功获取到锁。 + ++ 非公平锁 + + ReentrantLock reentrantLock = new ReentrantLock(false); + + reentrantLock 为new NonfairSync()实例 + + + +```java +//ReentrantLosck.lock +public void lock() { + sync.lock(); +} +// ReentrantLock.NonfairSync#lock +//公平锁是直接掉acquire(1) + final void lock() { + //直接尝试CAS更新状态变量 + if (compareAndSetState(0, 1)) + // 如果更新成功,说明获取到锁,把当前线程设为独占线程 + setExclusiveOwnerThread(Thread.currentThread()); + else + acquire(1); + } +//AbstractQueuedSynchronizer.acquire +public final void acquire(int arg) { + //尝试获取锁 + if (!tryAcquire(arg) && + //获取锁失败,加入失败队列 + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) + selfInterrupt(); + } +//// ReentrantLock.NonfairSync.tryAcquire() + protected final boolean tryAcquire(int acquires) { +// 调用父类的方法 + return nonfairTryAcquire(acquires); + } + +//ReentrantLock.Sync#nonfairTryAcquire +final boolean nonfairTryAcquire(int acquires) { + final Thread current = Thread.currentThread(); + int c = getState(); + if (c == 0) { + //如果状态变量的值为0,再次尝试CAS更新状态变量的值 + //相对于公平锁模式少了!hasQueuedPredecessors()条件 + if (compareAndSetState(0, acquires)) { + setExclusiveOwnerThread(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)一开始就尝试CAS更新状态变量state的值,如果成功了就获取到锁了; + +(2)在tryAcquire()的时候没有检查是否前面有排队的线程,直接上去获取锁才不管别人有没有排队呢; + +总的来说,相对于公平锁,非公平锁在一开始就多了两次直接尝试获取锁的过程 + +##### tryLock + +```java +//尝试获取一次锁,成功了就返回true,没成功就返回false,不会继续尝试 +public boolean tryLock() { + return sync.nonfairTryAcquire(1);//调用非公平锁的获取锁 +} +//尝试获取锁,并等待一段时间,如果在这段时间内都没有获取到锁,就返回false +public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + return sync.tryAcquireNanos(1, unit.toNanos(timeout)); +} +// AbstractQueuedSynchronizer.tryAcquireNanos() +public final boolean tryAcquireNanos(int arg, long nanosTimeout) + throws InterruptedException { + if (Thread.interrupted())//线程中断抛出异常 + throw new InterruptedException(); + return tryAcquire(arg) || // 先尝试获取一次锁 不同的锁调不同的方法 + doAcquireNanos(arg, nanosTimeout); + } +// AbstractQueuedSynchronizer.doAcquireNanos() +private boolean doAcquireNanos(int arg, long nanosTimeout) + throws InterruptedException { + if (nanosTimeout <= 0L) +// 如果时间已经到期了,直接返回false + return false; + //计算到期时间 + final long deadline = System.nanoTime() + nanosTimeout; + final Node node = addWaiter(Node.EXCLUSIVE); + boolean failed = true; + try { + for (;;) { + final Node p = node.predecessor(); + if (p == head && tryAcquire(arg)) { + setHead(node); + p.next = null; // help GC + failed = false; + return true; + } + nanosTimeout = deadline - System.nanoTime(); + if (nanosTimeout <= 0L) + //到期 + return false; + if (shouldParkAfterFailedAcquire(p, node) && + // 只有到期时间大于1000纳秒,才阻塞 + nanosTimeout > spinForTimeoutThreshold)//spinForTimeoutThreshold = 1000L + LockSupport.parkNanos(this, nanosTimeout);//阻塞 + if (Thread.interrupted()) + throw new InterruptedException(); + } + } finally { + if (failed) + cancelAcquire(node); + } + } +``` + +##### unlock + +```java +ReentrantLock.unlock +public void unlock() { + sync.release(1);//调用父类的AbstractQueuedSynchronizer.release +} +//AbstractQueuedSynchronizer.release + public final boolean release(int arg) { + //调用实现类的tryRelease()方法释放锁 + if (tryRelease(arg)) { + Node h = head; + //如果头节点不为空,且等待状态不是0,就唤醒下一个节点 + // 在每个节点阻塞之前会把其上一个节点的等待状态设为SIGNAL(-1) +// 所以,SIGNAL的准确理解应该是唤醒下一个等待的线程 + if (h != null && h.waitStatus != 0) + unparkSuccessor(h); + return true; + } + return false; + } + +//ReentrantLock.Sync#tryRelease + protected final boolean tryRelease(int releases) {//releases=1 + int c = getState() - releases; +// 如果当前线程不是占有着锁的线程,抛出异常 + if (Thread.currentThread() != getExclusiveOwnerThread()) + throw new IllegalMonitorStateException(); + boolean free = false; + // 如果状态变量的值为0了,说明完全释放了锁 +// 这也就是为什么重入锁调用了多少次lock()就要调用多少次unlock()的原因 +// 如果不这样做,会导致锁不会完全释放,别的线程永远无法获取到锁 + if (c == 0) { + free = true; + setExclusiveOwnerThread(null); + } +// 设置状态变量的值 + setState(c); + return free; + } +//AbstractQueuedSynchronizer#unparkSuccessor +private void unparkSuccessor(Node node) {//node头节点 + + int ws = node.waitStatus; + if (ws < 0) + // 如果头节点的等待状态小于0,就把它设置为0 + compareAndSetWaitStatus(node, ws, 0); +//头结点的下一节点 + Node s = node.next; + // 如果下一个节点为空,或者其等待状态大于0 即waitStatus=1 取消状态 + 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)将state的值减1; + +(2)如果state减到了0,说明已经完全释放锁了,唤醒下一个等待着的节点; + +##### newCondition + +```java +public Condition newCondition() {//reentrantLock + return sync.newCondition(); +} + final ConditionObject newCondition() {//sync + return new ConditionObject(); + } + public ConditionObject() { }//aqs +``` + +##### await + +```java +//AbstractQueuedSynchronizer.ConditionObject#await() +public final void await() throws InterruptedException { + if (Thread.interrupted()) + //线程中断抛出 + throw new InterruptedException(); + //添加节点到Condition队列并返回该节点 + Node node = addConditionWaiter(); + //完全释放当前线程获取的锁 + int savedState = fullyRelease(node); + int interruptMode = 0; + //是否在同步队列中 + while (!isOnSyncQueue(node)) { + LockSupport.park(this); + if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) + break; + } + if (acquireQueued(node, savedState) && interruptMode != THROW_IE) + interruptMode = REINTERRUPT; + if (node.nextWaiter != null) // clean up if cancelled + unlinkCancelledWaiters(); + if (interruptMode != 0) + reportInterruptAfterWait(interruptMode); +} + +//AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter + private Node addConditionWaiter() { + Node t = lastWaiter; + + if (t != null && t.waitStatus != Node.CONDITION) { + // 清除条件等待队列上节点状态不为 CONDITION 的节点 + unlinkCancelledWaiters(); + //清除之后重新获取尾节点 + t = lastWaiter; + } + // 新建一个节点,它的等待状态是CONDITION + Node node = new Node(Thread.currentThread(), Node.CONDITION); + + + if (t == null) + // 如果尾节点为空,则把新节点赋值给头节点(相当于初始化队列) + firstWaiter = node; + else + // 否则把新节点赋值给尾节点的nextWaiter指针 + t.nextWaiter = node; + lastWaiter = node;//尾节点指向新节点 + return node; + } + +//AbstractQueuedSynchronizer.fullyRelease + final int fullyRelease(Node node) { + boolean failed = true; + try { + int savedState = getState(); + if (release(savedState)) { + // 一次性释放所有获得的锁 + failed = false; + return savedState;//返回获取锁的次数 + } else { + throw new IllegalMonitorStateException(); + } + } finally { + if (failed) + node.waitStatus = Node.CANCELLED; + } + } + + +final boolean isOnSyncQueue(Node node) { + if (node.waitStatus == Node.CONDITION || node.prev == null) +// 如果等待状态是CONDITION,或者前一个指针为空,返回false +// 说明还没有移到AQS的队列中 + return false; + if (node.next != null) // If has successor, it must be on queue + //如果next指针有值,说明已经移到AQS的队列中了 + return true; + //从AQS的尾节点开始往前寻找看是否可以找到当前节点,找到了也说明已经在AQS的队列中了 + return findNodeFromTail(node); + } +``` + +##### signal + +```java +public final void signal() { + // 如果不是当前线程占有着锁,调用这个方法抛出异常 +// 说明signal()也要在获取锁之后执行 + if (!isHeldExclusively()) + throw new IllegalMonitorStateException(); + Node first = firstWaiter; + if (first != null) + // 如果有等待条件的节点,则通知它条件已成立 + doSignal(first); + + private void doSignal(Node first) { + do { + //移到条件队列的头节点往后一位 + if ( (firstWaiter = first.nextWaiter) == null) + lastWaiter = null; +// 相当于把头节点从队列中出队 + first.nextWaiter = null; + // 转移节点到AQS队列中 + } while (!transferForSignal(first) && + (first = firstWaiter) != null); + } + + final boolean transferForSignal(Node node) { + //把节点的状态更改为0,也就是说即将移到AQS队列中,如果失败了,说明节点已经被改成取消状态了 返回false,通过上面的循环可知会寻找下一个可用节点 + if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) + return false; + + //调用AQS的入队方法把节点移到AQS的队列中 + //注意,这里enq()的返回值是node的上一个节点,也就是旧尾节点 + Node p = enq(node); + int ws = p.waitStatus; + //如果上一个节点已取消了,或者更新状态为SIGNAL失败(也是说明上一个节点已经取消了) +// 则直接唤醒当前节点对应的线程 + if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) + LockSupport.unpark(node.thread); + return true; + } +``` \ No newline at end of file diff --git a/week_03/61/Semaphore.md b/week_03/61/Semaphore.md new file mode 100644 index 0000000..d64d3ea --- /dev/null +++ b/week_03/61/Semaphore.md @@ -0,0 +1,183 @@ + + +#Semaphore + +### 简介 + +Semaphore(信号量),内部维护一组许可证,通过acquire方法获取许可证,如果获取不到,则阻塞; +通过release释放许可,即添加许可证。 +许可证其实是Semaphore中维护的一个volatile整型state变量,初始化的时候定义一个数量,获取时减少, +释放时增加,一直都是在操作state。 +Semaphore内部基于AQS(同步框架)实现了公平或分公平两种方式获取资源。 +Semaphore主要用于限制线程数量、一些公共资源的访问。 + +### 源码解析 + +#### 内部类 + +##### Sync + +```java + //Semaphore.Sync +abstract static class Sync extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = 1192457210091910933L; +//构造方法,传入许可次数,放入state中 + Sync(int permits) { + setState(permits); + } +//获取许可次数 + final int getPermits() { + return getState(); + } +//非公平模式尝试获取许可 + final int nonfairTryAcquireShared(int acquires) { + for (;;) { + int available = getState(); + //剩余许可 + int remaining = available - acquires; + if (remaining < 0 ||//如果剩余许可小于0了则直接返回 + //如果剩余许可不小于0,则尝试原子更新state的值,成功了返回剩余许可 + compareAndSetState(available, remaining)) + return remaining; + } + } +//释放许可 + 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"); + //如果原子更新state的值成功,就说明释放许可成功,则返回true + if (compareAndSetState(current, next)) + return true; + } + } +//减少许可 + final void reducePermits(int reductions) { + for (;;) { + int current = getState(); + //即将减少的许可 + int next = current - reductions; + if (next > current) // underflow + + throw new Error("Permit count underflow"); + //原子更新state的值,成功了返回true + if (compareAndSetState(current, next)) + return; + } + } +//销毁许可 + final int drainPermits() { + for (;;) { + int current = getState(); + // 如果为0,直接返回 +// 如果不为0,把state原子更新为0 + if (current == 0 || compareAndSetState(current, 0)) + return current; + } + } + } +``` + +##### FairSync + +```java +static final class FairSync extends Sync { + private static final long serialVersionUID = 2014338818796000944L; + + FairSync(int permits) { + super(permits); + } +//尝试获取许可 + protected int tryAcquireShared(int acquires) { + for (;;) { + //公平模式需要检测是否前面有排队的 + //如果有返回失败 + if (hasQueuedPredecessors()) + return -1; + //没有排队更新state + int available = getState(); + int remaining = available - acquires; + if (remaining < 0 || + compareAndSetState(available, remaining)) + return remaining; + } + } +} +``` + +##### NonfairSync + +```java +static final class NonfairSync extends Sync { + private static final long serialVersionUID = -2694183684443567898L; + + NonfairSync(int permits) { + super(permits); + } +// 尝试获取许可,调用父类的nonfairTryAcquireShared()方法 + protected int tryAcquireShared(int acquires) { + return nonfairTryAcquireShared(acquires); + } +} +``` + +#### 构造方法 + +```java +public Semaphore(int permits) { + sync = new NonfairSync(permits);//创建时要传入许可次数,默认使用非公平模式 +} + +//传入许可次数,指定是否公平 +public Semaphore(int permits, boolean fair) { + sync = fair ? new FairSync(permits) : new NonfairSync(permits); +} +``` + +#### 其他方法 + +##### acquire + +获取一个许可,默认采用的是可中断的方,果尝试获取许可失败,会进入AQS的队列中排队 + +``` +public void acquire() throws InterruptedException { + sync.acquireSharedInterruptibly(1); +} +``` + +##### acquireUninterruptibly + +获取一个许可,非中断方式,如果尝试获取许可失败,会进入AQS的队列中排 + +``` +public void acquireUninterruptibly() { + sync.acquireShared(1); +} +``` + +##### tryAcquire + +尝试获取一个许可,使用Sync的非公平模式尝试获取许可方法,不论是否获取到许可都返回,只尝试一次,不会进入队列排队。 + +``` + +public boolean tryAcquire() { + return sync.nonfairTryAcquireShared(1) >= 0; +} +``` + +##### tryAcquire + +尝试获取一个许可,先尝试一次获取许可,如果失败则会等待timeout时间,这段时间内都没有获取到许可,则返回false,否则返回true; + +``` +public boolean tryAcquire(long timeout, TimeUnit unit) + throws InterruptedException { + return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); +} +``` \ No newline at end of file diff --git "a/week_03/61/java\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/week_03/61/java\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100644 index 0000000..1995eb9 --- /dev/null +++ "b/week_03/61/java\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,52 @@ +#JMM + +### Java 内存模型 + +Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。 + +### 主内存与工作内存 + +Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。 + +而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。 + +## 内存间交互操作 + +java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作 : + + + +- read:把一个变量的值从主内存传输到工作内存中 +- load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中 +- use:把工作内存中一个变量的值传递给执行引擎 +- assign:把一个从执行引擎接收到的值赋给工作内存的变量 +- store:把工作内存的一个变量的值传送到主内存中 +- write:在 store 之后执行,把 store 得到的值放入主内存的变量中 +- lock:作用于主内存的变量,它把主内存中的变量标识为一条线程独占状态 +- unlock:解锁,作用于主内存的变量,它把锁定的变量释放出来,释放出来的变量才可以被其它线程锁定; + +### 内存模型三大特性 + +1. 原子性 + +2. 可见性 + + 可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。JMM 内部的实现通常是依赖于所谓的内存屏障,通过禁止某些重排序的方式,提供内存可见性保证,也就是实现了各种 happen-before 规则。与此同时,更多复杂度在于,需要尽量确保各种编译器、各种体系结构的处理器,都能够提供一致的行为。 + + 主要有有三种实现可见性的方式: + + - volatile,会强制将该变量自己和当时其他变量的状态都刷出缓存。 + - synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。 + - final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。 + + 对前面的线程不安全示例中的 cnt 变量使用 volatile 修饰,不能解决线程不安全问题,因为 volatile 并不能保证操作的原子性。 + +3. 有序性 + + 有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。 + + volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。 + + 也可以通过 synchronized 来保证有序性,它保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码。 + + \ No newline at end of file diff --git a/week_03/61/synchronized.md b/week_03/61/synchronized.md new file mode 100644 index 0000000..2333c08 --- /dev/null +++ b/week_03/61/synchronized.md @@ -0,0 +1,45 @@ +#synchronized + +### 简介 + +synchronized是由一对monitorenter和monitorexit指令来实现同步的,在JDK6之前,monitor的实现是依靠操作系统内部的互斥锁来实现的,所以需要进行用户态和内核态的切换,所以此时的同步操作是一个重量级的操作,性能很低。 + +但是,JDK6带来了新的变化,提供了三种monitor的实现方式,分别是偏向锁,轻量级锁和重量级锁,即锁会先从偏向锁再根据情况逐步升级到轻量级锁和重量级锁。 + +### 实现原理 + +synchronized 就是使用 monitorenter 和 monitorexit 这两个指令来实现的。根据JVM规范的要求,在执行monitorenter指令的时候,首先要去尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,就把锁的计数器加1,相应地,在执行monitorexit的时候会把计数器减1,当计数器减小为0时,锁就释放了。 + +### 特性 + ++ 原子性、可见性、有序性 + ++ 可重入:同一个线程外层函数获取到锁之后,内层函数可以直接使用该锁 + ++ 非公平 + ++ 不可中断性 :如果这个锁被B线程获取,如果A线程想要获取这把锁,只能选择等待或者阻塞,直到B线程释放这把锁,如果B线程一直不释放这把锁,那么A线程将一直等待 + + ### 应用方式 + ++ 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁 + ++ 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁 + ++ 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁 + + ### **缺陷** + + **效率低:**锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时 + + **不够灵活**:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活 + + **无法知道是否成功获得锁,**相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,..... + + 所以后续有Lock类对**synchronized**弥补。 + + + + + + \ No newline at end of file diff --git a/week_03/61/volatile.md b/week_03/61/volatile.md new file mode 100644 index 0000000..36e3528 --- /dev/null +++ b/week_03/61/volatile.md @@ -0,0 +1,29 @@ +#volatile + ++ volatile特性 + + + 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性) + + + 禁止进行指令重排序。(实现有序性) + + + volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。 + ++ 总结 + + (1)volatile关键字可以保证可见性; + + (2)volatile关键字可以保证有序性; + + (3)volatile关键字不可以保证原子性; + + (4)volatile关键字的底层主要是通过内存屏障来实现的; + + (5)volatile关键字的使用场景必须是场景本身就是原子的; + + + + + + + + \ No newline at end of file diff --git "a/week_03/61/\345\206\205\345\255\230\351\227\264\346\223\215\344\275\234.png" "b/week_03/61/\345\206\205\345\255\230\351\227\264\346\223\215\344\275\234.png" new file mode 100644 index 0000000000000000000000000000000000000000..29bbc9de946b5550924504fc4587e787cff5608b GIT binary patch literal 7168 zcmeHLc{r4PzaK4Bl1fq-vQ#pmk?czmQWM&&F=ZcX80#=d*)yqR5FW`;gRy2QgKSx5 z(AXK0m@H!-Yh%tmp68tRea|1~darZNb)9pa>-;fu-}i6%?#u7@i8eOW=Q$vF00aW@ z7+kx08wA=#0p36F;{^VzTEvrp2Z#4z-XPGSdiIZ_*(28u1QO&kxO(NTzcro8?JC?;S-W`J%azMn?5fn$19!_V2fRD=3Z(sZ zU&4~HL6P}aUGd@LVb>A_k4io zPTL3qk89Cbwnn$baU8(ZtN-eh1tRF$hs2nJamjS`_6FLX&=&yVKN115g@RBXQ2tfe z?$93K%ZbfBPY<^HGNkbdy=gn Y2YW}wSB_WEpN zn9 >%eUY% z!SS#>%L*O-QULJDxg+d|yL=sB4+1?u3NQtM-T?;uhpEtSo6~s0&r#NP`M@sFgH3>L z@2!O2tq{W3&d%reLzoP&E?t2ZL{8e7W?}C*;h=@O;ttKNYo?}2?J1XT)F_o~Dm#*e z2Rv$+KMU&*y?kk?YCzUv1#N6}gvhQ@{W3oPUTT|6 m4JF)0Q7y6BZXeH@=7S{%n)0QRqMhZEw()Mo{xD@nAK( z`UoC*b@lVRckeE?YdOla0JbK*37Aawn#nJ3bs `7 zNFJ@fH FKZq0onWr0GA zfv`Ike_*nNta{^+>aSB9Jp}Y=0&(+0fTR{iYrA5F!DtEN63I2ibAToQn@Yv?!9%P@ zt1{ `E&ky4e~el*5TL`lSVLi#|VUO?C!dmKZXn{%%mix6s0UtQ|A#k6Ebib%I5 z$tj^gAp6U(-A<@!;xAi_hKY6H)ajBOjo{6xoTYJ5mYJaj u=^i{|sDnRwGh1%jN*@GT1r#n#yP2vu1%L4t9d^Cz9{wJ)4X8Q3 z> zol6t>jMTeY;pn$Ef%L&L2X4> !3#l76eu`-}g86foBYzK<% zy~dv@rO0gMV$nd5kdDt5-{O?!XXmyX$o>joC85#H-g3LGRk}l)CZJ%>{YQ0s9k_kP zaA9n(l|FSF`q<(}Tn3le=Y(u8XUL;gU70S;4~1+5r62%vOXGQe|WhDKaSLLZsHK zNKtgZYn@1gg0V@1!t?I+=XYHM+QOhEbU~r#*R!Q{#vLrZW%Kw)4|cBx?#1jU>Q&~M z6=}OyGfYbC2}2$=R is(?xbimL-lS$%5q(Z>rg0rL zx|RN$IUk~A={tm)ueR?iuyWuwe@}pMH9jpU(Lx$-?dj;NSWd!nwgOS|;<_MZvf@{4 z;LYKg?q7+;ow|eWYJPLwqHCieAz N0On+#IXP%4oSz0j|x$Ww7%=HKr?w}Tus$h zlB>T@@7O%--HWEuw`&`6O?Lza-uK6!oYzQyEhH@bicB^PMm4`t@&4JRFWNKYTth;y zmSjw!auV{JpkU8}%JFTG&_;NndGr|^N5d&x*gG5_g=6k|654&Xu_EZq*|XVMSvrxW zn=}fQnqS_7PNn(|xp{3&B&EvO*>6H9=rL3-Kj2eaz;kBpjq+Vj2e*bRP_k|B2?V3a zQZ?rx- SCQq-EdKqjRl=M!s|i> zY5DD!nUFJ~O=rMn;%zTBc@vTpU(^whTczqq4m|&NhW8WzKet-@Yuw8NQ&NpIj9( z=sluFn9v{xc|@t^aF}7RoVZ5w;BNw^m!wm41isfo1AhxR>3 b7>6G9Zpi z{sOxT!^g6!d}iJO(Wk1a`mUtpj_=$sLPS>(Facj2f-H7b3`ZBSQMGJPxP$eywXpit z(^ykpZVF#V1qQym{r;MtUwK@7d`gJW^Wy;BTrD_ROhE@Wq|t!<7=^%H7;h@ATJZDn znTpkV$vjN)#cVE52-~EPLWFpl*wQ$#D=u**FF8F~KG5flzz_r^(o)oNKRu(zEB* zgioFvLw6iJva^}-Ls w)BfacDmW(In+xc zi)KU}7%i(G%FN5Ve>cHCYi^ZUoVPmf5sXSH3CKR5cmCy8mWYc2A|t8(Fy-1?Cnz&1 zn}3SVy7SXEdU6qd!($?6DJi#E@a7 aXo? z>n)}-bFSHs5hh4c&4U3uW|K+tG_TI}?Sdm~T%-KoVADr{tZ~98BETg_x`Z#P+7PNn zUDfd2U1HW55%}I{n?ZACd?0=DEeX~M~!CAv*!L=XWS>ugGUsr8+CY~ z&m;#g;e0OWr(tg5(A-}$>>fQ_ffn(VFa6%i=oOJ0kr8qJOKzaERA~JCz(c|;6DKFP zCKTqEb;8_ xWRp7KlN9pIY;W-3MP+tmPrWvLfi)?bj)wD vF} @7m_Nh2!n&>;+1zBM19 znJYAZghz7e^|M@TFTS6oY0BK5_Pt+yhMi00NM8w@Mo08BMD>fH_`DI}eh+Jj6Sd?R zp9jMowk99yISgS*Hy3RNt4&oOb(A{Z&WEKc`;rrDXCiE>h)2N!fYE$P`rUyfzxMVn z)a^&VO5bk$*Gy~y;c9cR>>6=y65iI%!_Mrg?3enmPop65CM)~&D&D~@!LSY;SiRJJ z7~Xc}fcI;to5^!(q6>dV8U20pZfL$~p@7Uwdt%>$(L(RbWI-E2gHjLJ*~)zJ3rC>$ zd96ww&l0(#VrYY_-Q!`{UPwQ8h;*NBp8KZ~%X#S+l%2G(R1IiAcDI&>d|5Nbi{Xys z15Tc^J4Ts{L0$;DX*|=n*i@=XL}#oN-{SkR|LlCCGr;AQjs2ii!l4Mle&=9Rgc2@i zTS9m3(sa3(Wb!2361~$Q+^0&FaeNU4@l&0BhkvVk5_Dln&+?&QA5T0%HuW&^`d;tw z4jNj+Z^IOqw!grAD>4yrESdqWz6(sSE*M98(-+aK*3yf0J#MT*c`Hr8<2gN0y5@1~ zTP9mMtGam)jHp`RP4$uS_P*O{@mN{RHv&vwQv} y{5r-jYbcFBs|%jWQs(c?gC+4msX@pSe3ZfyP pu&{6oNF@fDDKL+GwL?vI7oit2>>5T?s3oB*~M2Wr0TUuQeXn#IF4R z)ZoAg;E2}JVo<`b$L=r+_n#33{(;md4;ne`+rYp%Fc|!;ukWmcghXcSxt%I+oDwXz z!|2ae29zSGs;V@VO5LQV1_lPM>@5Sj8gQ7+kt^Kk{`Eb-P<1$#g$~RTC>uK(WNo|B zGO8NZtkYq>l%Ae!xm;jn)^{j=` zV(`uicBG$xzBeE0sxU;4+3CAZeDyurZ!!Y@?$98HTmDmI1)D+4-!SFF#%vT62*j{>|s zBG9JDcNQqu2kll=-<(SVXnxE3)ZPoP4FhZgvkt=2)V95)riWKE;A8WA(?KgAGDg!o z_&i1W;8~Yw9wx(BhgrZWYQ2pLwk*3FR+75~E_rt?M-+Q!=CyWK3}ohx51T29(Q9gZ zWk00HB7l34ZXc@7M{W=*Ft$0PFPxMBoKhXnXDmT!DYQsSd)pA^oA~XArqTZH4I9?H zOHqAM%0#Ni*6qwod?jvlQHB;iw;4OjHBcyx3v )5T%4#=yhAU@81^rjh3O-*%) z-ntK51;=ZGzr>w&qBdu_vy9(xBj{MpI(kii6W8X7O_k> i7<)EY3&C3*R`^GPPqd`&OBzUL8&RC&}r3BSe
mm$tfSX; z01up&S*Wr2K|LryTVqX|My@sUw^#L#OOJ}hdwjlfpD&=u$3ws~XEh}cw%gp>B6EzNjld0ld%Tz1j+AoAp-pzE+#`t)Om|rtixf@KkdkHts-@XU zaJ2dAu(wgLQIXqm84F3J(V&t6?F2TM_lKLtVguji_LROzK*Q`gA^>ka9u0Ilva_*_ zjcpOP8HBdhL tR7`l!!Mbo( AV&<%gUkold%Mvs!zcjTcJAsm)dBOp? n3p*xy7m`dA+G=6_P#m;U^qADKFw>IS0UdA zt$iBpsp(lLZj+^y`_`g& $WvNM-4(#Kf?7M $ay0T#w+4YTgG%xanOdB z%GPWNV+J2(D%vCWppv;d#3+&yV4K)XU$?j_XA?l}UZlEL84!-)L7IR+7}JYN#6=#U zcq9OT2eTJ7UEcWtBmITeo-6^7!VsTb1*3*No2P~hx;^YZFSSA@lxjAfq47jh>wxTh zce%RuQ~y2a`9A^2N69OmO1!Si+0^b!V#DUgd0U #@9RrV?+<6W3r_2X{6d;rm$Ur1)^Q25ymT4QnRqL~iVr%j|nk z#{{{Vg|l 7 分布式锁 + +### 简介 + +为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题! + +### 什么是分布式锁 + +- 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。 +- 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。(我觉得分布式情况下之所以问题变得复杂,主要就是需要考虑到网络的延时和不可靠。。。一个大坑) +- 分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。。 + +### 分布式锁应具备的条件 + ++ 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行 + ++ 高可用的获取锁与释放锁 + ++ 高性能的获取锁与释放锁 + ++ 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误) + ++ 具备锁失效机制,防止死锁 + ++ 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失 + + ### 应用方式 + ++ [基于数据库实现分布式锁](https://mp.weixin.qq.com/s?__biz=Mzg2ODA0ODM0Nw==&mid=2247483982&idx=1&sn=305e22c0e74a028cb9180ac03bd110fa&scene=21#wechat_redirect); + ++ [基于缓存(Redis等)实现分布式锁](https://mp.weixin.qq.com/s?__biz=Mzg2ODA0ODM0Nw==&mid=2247483993&idx=1&sn=f6b408b01e10a2ab75256acf07e08537&scene=21#wechat_redirect); + ++ [基于Zookeeper实现分布式锁](https://mp.weixin.qq.com/s?__biz=Mzg2ODA0ODM0Nw==&mid=2247483988&idx=1&sn=9e29b4aa64becd7d7764062dc5389eed&scene=21#wechat_redirect); + + + + + + + + \ No newline at end of file -- Gitee