当前位置: 首页 > news >正文

网站忘了怎么办网站集群建设通知

网站忘了怎么办,网站集群建设通知,绵阳市住房和城乡建设局网站,seo的作用主要有一、AQS 很多人都没有讲明白 #x1f914; 翻看了网上的 AQS#xff08;AbstractQueuedSynchronizer#xff09;文章#xff0c;质量参差不齐#xff0c;大多数都是在关键处跳过、含糊其词#xff0c;美其名曰 “传播知识” 。 大多数都是进行大段的源码粘贴和注释 翻看了网上的 AQSAbstractQueuedSynchronizer文章质量参差不齐大多数都是在关键处跳过、含糊其词美其名曰 “传播知识” 。 大多数都是进行大段的源码粘贴和注释或者叫源码翻译有必要写上一篇文章将 AQS 的一些基础原理搞清楚搞正确。 本文尽量用图文的形式阐释过程同时结合断点调试将 AQS 在多线程运行下的状态尽可能呈现出来 二、准备和前提 注意本文所指的 AQS 均为 Java 并发包中的 AbstractQueuedSynchronizer 类。 2.1 环境说明 InterlliJ IDEA 2024.2 (免费使用 30 d)JDK1.8 2.2 线程知识储备 知识点一、 LockSupport 深入 AQS 的源码需要提前理解 LockSupport 接口 LockSupport.park() : 当前线程会进入阻塞状态直到它被 unpark 唤醒或者线程被中断 LockSupport.unpark(Thread thread): 唤醒其他线程参数是 Thread LockSupport 功能简单强大对线程的挂起和唤醒非常方便。 知识点二、 ReentrantLock ReentrantLock 依赖 SyncSync 继承 AQSFaireSync 是公平锁; NonFaireSync 是非公平锁, 也是 RentrantLock 默认的锁类型 模板代码如下 // 获取锁 lock.lock(); try {// 代码块 } finally {// 释放锁lock.unlock(); } 知识点三、CAS 常用的原子操作用于在多线程编程中实现无锁的线程安全操作CAS 操作包含三个主要的参数内存位置V、预期原值A和新值B只有当内存位置的值与预期原值相匹配时CAS 操作才会将该位置值更新为新值并且这种检查和替换是作为一个不可分割的原子操作完成的。 知识点四线程模式 模式 含义 SHARED 线程以共享的模式等待锁 EXCLUSIVE 线程正在以独占的方式等待锁 2.3 代码准备 需要断点调试本文准备了一段代码可以按照文章步骤逐一调试。 代码内容三个线程分别为 ABC进行锁资源的抢夺。本文使用 ReentrantLock 中的非公平锁实现。 代码如下 ABC 三个线程共同争夺一把锁获得锁后执行 count完成后释放锁 为了有更好的阅读体验建议先搞明白下面代码☺️。 import java.util.concurrent.locks.ReentrantLock; public class LockExample {// ABC 三个线程抢夺一把锁。显示指明使用非公平锁private static final ReentrantLock lock new ReentrantLock(false);// 获取锁后对 count 进行 操作private static volatile int count 0;public static void main(String[] args) throws InterruptedException {// 线程 A Thread a new Thread(() - {for (int i 0; i 10; i) {// 获取锁lock.lock();try {count;System.out.println(Thread.currentThread().getName() incremented count to count);} finally {// 释放锁lock.unlock();}}}, A);// 线程 B Thread b new Thread(() - {for (int i 0; i 10; i) {// 抢占锁lock.lock();try {count;System.out.println(Thread.currentThread().getName() incremented count to count);} finally {lock.unlock();}}}, B);// 线程 B Thread c new Thread(() - {for (int i 0; i 10; i) {// 抢占锁lock.lock();try {count;System.out.println(Thread.currentThread().getName() incremented count to count);} finally {lock.unlock();}}}, C);a.start();// 先让 B 线程晚一点执行System.out.println(---------);Thread.sleep(20000);b.start();// C 线程最后执行System.out.println(---------);Thread.sleep(20000);c.start();a.join();b.join();c.join();} } 可以拷贝到自己的 IDEA 中进行调试 2.4 如何进行多线程的 debug 很多同学没有多线程的调试经验当然多线程的调试是有难度。 希望通过本文能够有一些帮助。 本文中的多线程调试需要掌握两个关键要的 要点一查看运行栈帧 切换线程 在 Threads Variables 这个窗口保障线程之间切换。 要点二断点暂停方式选择 Thread 这个是最为重要的。 建议本次调试选择 Make Default点击图中 Make Default后续所有断点都是 Thread如果不选择 Thread则无法进行断点追踪 接下来通过断点追踪的演示 AQS 内部执行过程和原理。 将整个过程分为两个大阶段抢锁过程和释放锁过程 ABC 三个线程抢锁过程分别是 A 先抢到锁然后 B、 C 再进入抢锁A 获得锁执行代码后再释放锁, 然后将 B 线程唤醒 最后再唤醒 C 三、抢占锁过程演示 3.1 线程 A 获取锁演示 场景一 模拟先让 A 线程获取到锁 注意在 B、C 开始的位置设置断点这样能够控制 B、C 线程的启动时间。 先让断点执行到线程 A并停留在线程 lock.lock() 位置。接下来进入此方法 注意只有切换到线程 A 才能进入到线程 A 的 lock 的源码。 点击进入源码注意断点位置 可以参考下图 一些非关键的代码理解会直接跳过比如 sync.lock(); 关键代码如下 final void lock() {// CAS 方式的设置 stateif (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1); } 通过 CAS 将 state 设置为 1设置成功这个线程则获得锁成功。 深入看一下 compareAndSetState 代码 是通过 unsafe 的 compareAndSwapInt 实现的。 unsafe 类是一个功能较为底层。但并不复杂使用上可以模仿 protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update); } stateOffset 是对象 state 字段的偏移值。 stateOffset unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField(state)); 总结一下 使用 unsafe.compareAndSwapInt(this, stateOffset, expect, update) 对 state 字段进行值的更新如果成功则获取锁成功否则进入其他分支。 BC 线程还未开始只有 A 线程处于 RUNNING 状态即 1 个线程因此 state 可以给更新成 1。 通过断点运行compareAndSwapInt 返回 true 接下来再调用 setExclusiveOwnerThread(), 完成线程 A 独占访问。 设置线程独占 运行到现在AQS 中的情况如下图所示 A 获得独占锁BC 未开始 因为只有一个线程A所以没有争抢不需要创建队列。 接下来暂停线程 A 的调试模拟线程B 抢锁过程。 3.2 线程 B 抢占锁演示 执行过程 先切换到 main 线程启动线程 B 当线程 B 启动后切换到线程 B 断点调试执行线程 B 的抢占过程 compareAndSetState(0, 1) 返回为 false执行 acquire(1)代码 步骤一切换线程 main 线程让线程 B 运行。 步骤二当线程 B 运行成功后切换线程 B进行断点追踪。 线程 B 进入了 acqurie(1) 分支 分析 acquire 代码逻辑 流程tryAcquire 尝试再一次获取锁如果失败进入队列 进入 tryAcquire 源码进行查看 final boolean nonfairTryAcquire(int acquires) {// 获取当前线程final Thread current Thread.currentThread();int c getState();if (c 0) {// 当为 0 的时候表示没有线程在使用锁尝试 CAS 抢锁if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 是否当前线程实现线程的重入 else if (current getExclusiveOwnerThread()) {int nextc c acquires;if (nextc 0) // overflowthrow new Error(Maximum lock count exceeded);setState(nextc);return true;}return false; } 如果 state 0表示还没有独占的线程。于是尝试将当前线程设置成独占这段代码与刚刚分析的逻辑是一样的做一次尝试 final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1); } 有两个点可以提出来关注一下 能够设置 state 值成功的线程即抢占锁成功后续不用进入队列等待如果获得锁的线程执行完毕即 state 0 于是同时新来一个线程这个线程将尝试获得锁而不是将其加入到队尾。这一点体现了不公平锁的特性不是按照FIFO 特别说明如果 state 0 并不表示队列中没有正在等待的线程。 如果当前线程与获得独占锁的线程是同一个线程允许 state 修改。 else if (current getExclusiveOwnerThread()) {int nextc c acquires;if (nextc 0) // overflowthrow new Error(Maximum lock count exceeded);setState(nextc);return true; } 因为是相同的线程所以这段代码也是线程安全的。 注意这也是 ReentrantLock 能够实现重入的关键代码。 如下面进行两次 lock。 那么 state 会变成 2。 public void performAction() {lock.lock(); // 第一次获取锁try {System.out.println(First lock acquired.);performAnotherAction(); // 调用另一个需要锁的方法} finally {lock.unlock(); // 释放锁System.out.println(First lock released.);} }public void performAnotherAction() {lock.lock(); // 第二次获取同一个锁try {System.out.println(Second lock acquired on the same thread.);// 执行一些需要锁的操作} finally {lock.unlock(); // 释放锁System.out.println(Second lock released.);} } 当线程不能获取到锁的时候则进入 addWaiter 环节 if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 先调用 addWaiter()再调用 acquireQueued 3.2.1 添加等待节点 addWaiter() 和 acquireQueued() 是实现线程等待的关键 现在继续分析线程 B 的执行过程: 通过代码分析 pred 为 null线程 B 进入 enq(node) 注意现在的 Node 为线程 B 3.2.2 enq() 创建队列 进入 enq 方法的线程依然存在多个线程竞争的关系。 private Node enq(final Node node) {for (;;) {Node t tail;if (t null) { // Must initializeif (compareAndSetHead(new Node()))tail head;} else {node.prev t;if (compareAndSetTail(t, node)) {t.next node;return t;}}} } 通过 for(;;) 保证所有进来的线程最终都能够被合理的添加到相应的节点上。 分析线程 B 在这段代码中的过程 线程 B 第一次进入 t null 为 ture将创建了一个没有任何线程绑定的节点 Node 暂且称为空信息节点第二次设置线程B 节点为尾部节点并将头节点的 next 设置为 线程 B 节点 上面是理想情况进入 enq 依然是存在多线程的所以需要通过 CAS 保证线程的运行安全。 第一次执行enq() 第一次执行完成头部节点地创建。当前 AQS 的状况 完成后将 head、tail 地址引用的方式指向刚刚 New Node() 的节点。head、tail 都是地址引用 注意这个头节点没有任何线程信息这一点在其他 blog 中说法错误的要警惕 由于使用的是for(;;) 逻辑 在进入第二次的时候线程 B 这个节点将会插到队尾。 hashCode699 线程 B 节点 hashCode702 空信息头节点 在循环完成第二次后完成对线程 B 节点的插入。如下所示 tail 设置成线程 B 节点线程 B 节点的的 prev 指向了 head(即刚刚创建出来的空信息节点) 完成了队列的创建后看一下接下来线程 B 到底是立刻等待还是需要执行一些特殊逻辑后再进入等待。 3.2.3 acquireQueued 注意关键代码for(;;) if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt())interrupted true; 先执行 shouldParkAfterFailedAcquire如果 shouldParkAfterFailedAcquire() 返回为 false则 parkAndCheckInterrupt() 不会再执行 shouldParkAfterFailedAcquire 判断是否需要将线程暂停。注意传入参数 补充知识点 枚举 含义 0 Node 初始化的时候的默认值 CANCELLED 为1表示线程获取锁的请求已经取消了 CONDITION 为-2表示节点在等待队列中节点线程等待唤醒 PROPAGATE 为-3当前线程处在 SHARED 情况下该字段才会使用 SIGNAL 为-1表示线程已经准备好了就等资源释放了 具体逻辑情况如下 特别注意waitStatus 0 只有取消状态这种状态 /** waitStatus value to indicate thread has cancelled */ static final int CANCELLED 1; 第一次执行 acquireQueued shouldParkAfterFailedAcquire() 返回 false。 AQS 中的状态信息完成第一次运行后线程 B 节点的前驱节点的 waitStatus -1 。 如下图所示 第二次执行 acquireQueued shouldParkAfterFailedAcquire() 返回 true。 原因是因为前驱节点的 waitStatus -1 第一次shouldParkAfterFailedAcquire(p, node) 执行 false方法 parkAndCheckInterrupt()将跳过 第二次shouldParkAfterFailedAcquire(p, node) 执行 true 则执行 parkAndCheckInterrupt()对线程 B 进行暂停处理。 很显然在线程进入阻塞等待之前线程节点多做了一次循环算是一种优化 由于线程 B 一直获取不到锁执行 park 对线程阻塞挂起 完成线程 B 的抢占演示后再演示线程 C 3.3 线程 C 抢占锁演示 线程 C 的逻辑和线程 B 的逻辑是相似的 A 正在获取锁线程 C 只能执行 acquire() 分支代码 线程 C 和线程 B 一些差异点 3.3.1 addWaiter 方法 由于 tail 已经存在则直接将节点添加到队尾 执行完成后 AQS 的情况如下 注意head、tail 只是地址引用。分别指向队列的首尾。而 head 并不是一个实际的线程节点没有线程相关信息 这个要特别注意 通过栈帧情况线程 A 处于运行状态线程 B、线程 C 都处于挂起等待状态。 3.4 抢占过程总结 设置 state 0 成功的线程AQS 的 exclusiveOwnerThread 值将被设置成该线程。即这个线程获得锁ReentrantLock 可以重入通过 state 来控制重入次数ReentrantLock 的非公平锁的原理当新的线程进入调用 tryAcquire多次尝试对 state 修改即尝试获得独占这个时候不管是否存在阻塞线程如果多次尝试没有获取独占机会会将这个线程加入双向队列注意双向队列的头节点是一个不带线程信息节点在线程进入阻塞状态之前依然会判断是否能够获取到锁。如果再次失败最终会线程进行阻塞挂起 线程 ABC 的锁抢占过程就演示完成那么阻塞挂起的线程又如何被唤醒呢 四、锁释放过程 4.1 释放逻辑 接下来执行线程 A 中的释放逻辑: 调用情况如下 java.util.concurrent.locks.ReentrantLock#unlock public void unlock() {sync.release(1); } 代码逻辑 释放锁exclusiveOwnerThread 设置为 nullstate 释放对应数值唤醒等待的线程 public final boolean release(int arg) {if (tryRelease(arg)) {Node h head;if (h ! null h.waitStatus ! 0)// 唤醒等待的线程unparkSuccessor(h);return true;}return false; } 4.2 唤醒等待线程 关键方法 unparkSuccessor() 这个时候 AQS 的状态情况 state 0exclusiveOwnerThread null 线程 B 被唤醒 关键方法如下 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);// 注意LockSupport.park 会响应中断// 非中断返回 falsereturn Thread.interrupted();} interrupted()是一个静态方法它检查当前线程是否被中断并清除中断状态。这意味着如果线程被中断第一次调用interrupted()会返回true并且重置中断状态。因此如果紧接着再次调用interrupted()它将返回false因为中断状态已经被清除了 4.3 线程 B 获得锁 线程 B 继续执行逻辑 线程 B 获得锁执行代码线程 C 再获得锁执行代码。由于线程 C 的释放过程和线程 B 是一样的就不必备再赘述。 当线程 ABC 都释放后这个时候 AQS 的状态 最后 AQS 的状态如下 注意 head、tail 并没有销毁将常驻内存 4.4 释放过程总结 Lock.park 挂起线程Lock.unpark 唤醒线程当线程执行完成后会唤醒最靠前的那个线程节点注意不是 head 节点。 head 节点是不具备线程信息的整个释放过程类似链表的删除 4.5 公平锁 faireSync 公平锁逻辑稍微简单一些公平锁的逻辑则完全按照队列的思想来FIFO 思路判断队列是否存在如果不存在则创建如果存在则加入到队尾 五、总结 到这里对于 AQS 的队列、线程阻塞等问题基本算是清楚了。如果不清楚可以按照代码一步步地进行调试感受一下 AQS 的魅力所在。 5.1 锁抢占过程和释放过程 第一个线程通过 CAS 设置 state 和 exclusiveOwnerThread 获得锁第二个线程创建一个双线队列同时第一个节点为空信息节点 head不携带线程相关信息同时将该线程封装成 Node插入到队尾第三个线程会继续插入到队尾进入队列的线程会通过 LockSupport.park 进入挂起阻塞状态当获得锁的线程执行完调用释放锁的过程会通过 LockSupport.unpark 将第一个线程节点唤醒。注意不是 head 节点。 其他线程唤醒过程类似。 5.2 AQS代码设计中的优点 设计模式AQS 中的模版方法通过 tryAcquire、tryRelease 等方法的重写从而实现了不同的工具类。很显然结构设计是很棒的CAS使用大量的 CAS 空控制线程安全通过 for(;;) 等细节多次尝试对锁的获取避免直接将线程挂起阻塞细节上很讲究通过精心的变量值设计诸如 state、waitStatus 等代码更少逻辑更清晰 5.3 未涉及知识和弊端 部分细节未深入了解主要有以下 中断获取 acquireInterruptibly条件判断 condition异常情况以 ReentrantLock 进行讲解有一定局限 如果有时间再挖一挖。 5.4 为什么队列中的 head 不携带线程信息 问题队列中的 head 节点不绑定线程为什么需要这样的 head 节点存在 答案一些自己的看法(不一定对和全欢迎补充) 由于线程的生命周期非常短如果 head 是线程节点那么随着锁的争夺和释放整个队列将被反复创建和销毁。 但是给一个无关的 head 信息只创建一次并能反复使用常驻内存。 从整体而言确实可以带来性能的提升 工作这么多年第一次对 AQS 有这么深入的理解不多废话了本文到此结束 已经同步发布到微信公众号面汤放盐网上的 AQS 文章让我很失望
http://www.dnsts.com.cn/news/129689.html

相关文章:

  • 网站一般宽度是多少像素网站开发工程师认证
  • 网站开发主要做哪些自己网站让百度收录怎么搜索会展示
  • 网站用户角色湘潭网站建设 找磐石网络一流
  • 百度网站建设策划书范文怎么做网站的后台管理系统
  • 网站网页设计公司合肥专业做网站
  • 泰州网站制作报价wordpress一级域名
  • 沧州做网站公司武陟县住房和城乡建设局网站
  • 山西cms建站系统价格网站建设公司成都案例展示
  • 淮安网站设计学做网站的书哪些好
  • 汕头网站制作服务商网站服务器空间选择
  • 南昌网站外包有创意的网络营销案例
  • 哈尔滨网站制作网页wordpress固定链接标签加上页面
  • 手机怎么做弹幕小视频网站桂林 网
  • 冠县做网站哪里好做网站大型
  • 网站开发前台代码和后台代码安卓优化大师app下载
  • 北京网站建设服务器维护wordpress 支付下载
  • 郑州网站建设zzwzjs仿win8网站
  • 前端做网站使用的软件工具湛江市手机网站建设企业
  • php网站建设参考文献建筑工程机械人才培训网
  • 甜点网站里的新闻资讯怎么做wordpress排版插件
  • 帮别人做高仿产品网站 违法么网站特殊字体
  • 做a视频网站有哪些建设大型网站设计公司
  • 建立网站软件自己的服务器如何做网站
  • 网站建设选哪家公司好优化网站推广排名
  • 网站建设了流程wordpress网站重做
  • 个人网站建设分几个步走要求维护公司做网站整改的函
  • 忻州 建网站wordpress app 加载慢
  • 网页建站分为几个类型网站建设php有哪些
  • 怎样做网站模板简单html代码
  • 网站建设话术北京建筑信息网