游戏网站建设的策划app线上推广
2026/4/6 6:05:04 网站建设 项目流程
游戏网站建设的策划,app线上推广,wordpress标签说明,网络推广文案案例八股篇#xff08;1#xff09;#xff1a;LocalThread、CAS和AQS ThreadLocal ThreadLocal 的作用 线程隔离#xff1a;ThreadLocal 为每个线程提供了独立的变量副本#xff0c;这意味着线程之间不会互相影响#xff0c;可以安全地在多线程环境中使用这些变量。降低耦合…八股篇1LocalThread、CAS和AQSThreadLocalThreadLocal 的作用线程隔离ThreadLocal为每个线程提供了独立的变量副本这意味着线程之间不会互相影响可以安全地在多线程环境中使用这些变量。降低耦合度在同一个线程内的多个函数或组件之间使用ThreadLocal可以减少参数的传递降低代码之间的耦合度使代码更加清晰和模块化。性能优势由于ThreadLocal避免了线程间的同步开销所以在大量线程并发执行时相比传统的锁机制它可以提供更好的性能。publicclassThreadLocalExample{privatestaticThreadLocalIntegerthreadLocalThreadLocal.withInitial(()-0);publicstaticvoidmain(String[]args){Runnabletask()-{intvaluethreadLocal.get();value1;threadLocal.set(value);System.out.println(Thread.currentThread().getName() Value: threadLocal.get());};Threadthread1newThread(task,Thread-1);Threadthread2newThread(task,Thread-2);thread1.start();// 输出: Thread-1 Value: 1thread2.start();// 输出: Thread-2 Value: 1}}ThreadLocal 原理了解吗publicclassThreadimplementsRunnable{//......//与此线程有关的ThreadLocal值。由ThreadLocal类维护ThreadLocal.ThreadLocalMapthreadLocalsnull;//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护ThreadLocal.ThreadLocalMapinheritableThreadLocalsnull;//......}从上面的Thread类源代码可以看出Thread中有一个threadLocals和一个inheritableThreadLocals变量它们都是ThreadLocalMap类型的变量。默认情况下这两个变量都是 null只有当前线程调用ThreadLocal类的set或get方法时才创建它们实际上调用者两个方法的时候我们调用的是ThreadLocalMap类对应的get()、set()方法。ThreadLocal类的set()方法publicvoidset(Tvalue){//获取当前请求的线程ThreadtThread.currentThread();//取出 Thread 类内部的 threadLocals 变量(哈希表结构)ThreadLocalMapmapgetMap(t);if(map!null)// 将需要存储的值放入到这个哈希表中map.set(this,value);elsecreateMap(t,value);}ThreadLocalMapgetMap(Threadt){returnt.threadLocals;}通过上面内容我们足以通过猜测得出结论最终的变量是放在了当前线程的ThreadLocalMap中并不是存在ThreadLocal上ThreadLocal可以理解为只是ThreadLocal的封装传递了变量值。ThreadLocal类中可以通过ThreadLocal.currentThread()获取到当前线程对象后直接通过getMap(Thread t)可以访问到该线程的ThreadLocal对象。每个Thread中都具有一个ThreadLocalMap而ThreadLocalMap可以存储以ThreadLocal为 keyObject 对象为 value 的键值对。ThreadLocalMap(ThreadLocal?firstKey,ObjectfirstValue){//......}比如我们在同一个线程中声明了两个ThreadLocal对象的话Thread内部都是使用仅有的那个ThreadLocalMap存放数据的ThreadLocalMap的 key 就是ThreadLocal对象value 就是ThreadLocal对象调用set方法设置的值。ThreadLocalMap是ThreadLocal的静态内部类。ThreadLocal 内存泄漏问题怎么导致的ThreadLocal内存泄漏的根本原因在于其内部实现机制。因为每个线程维护一个名为ThreadLocalMap的 map。当你使用ThreadLocal存储值时实际上是将值存储在当前下称的ThreadLocalMap中其中ThreadLocal实例本身作为 key而要存储的值作为 value。ThreadLocalMap的Entry定义如下staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);valuev;}}ThreadLocalMap的 key 和 value 引用机制key 是弱引用ThreadLocalMap中的ThreadLocal的弱引用WeakReferenceThreadLocal。这意味着如果 ThreadLocal 实例不再被任何强引用指向垃圾回收器会在下次 GC 时回收该实例导致ThreadLocalMap中对应的 key 变为 null。value 是强引用即时 key 被 GC 回收value 仍然被ThreadLocalMap.Entry强引用存在无法被 GC 回收。当ThreadLocal实例失去强引用后其对应的 value 仍然存在于ThreadLocalMap中因为Entry对象强引用了它。如果线程持续存活例如线程池中的线程ThreadLocalMap也会一直存在导致 key 为 null 的entry无法被垃圾回收造成内存泄漏。虽然ThreadLocalMap在get()set()和remove()操作时会尝试清理 key 为 null 的entry但这种清理机制是被动的并不完全可靠。如何避免内存泄漏的发生在使用完ThreadLocal后必须调用remove()方法。这是最安全和最推荐的做法。remove()方法会从ThreadLocalMap中显式地移除对应的entry彻底解决内存泄漏的风险。即使将 ThreadLocal 定义为static final也强烈建议在每次使用后调用remove()。在线程池等线程复用的场景下使用try-finally块可以确保即使发生异常remove()方法也一定会被执行。CAS什么是CASCAS 即比较并交换CompareAndSwap它包含三个操作数变量内存地址V表示旧的预期值A表示准备设置的新值B表示执行 CAS 操作的时候只有当VA时才会去用B去更新V的值否则不会执行更新操作。CAS 是一条 CPU 的原子指令cmpxchg不会造成数据不一致的问题。Java 的 Unsafe 提供的 CAS 操作CompareAndSwapXXX底层实现即为CPU指令cmpxchg。CAS有什么缺点ABA 问题变量值在操作过程中先被其他线程由 A 修改为 B又被改回 ACAS无法感知中途变化导致操作为误判为“未变更”。比如线程1读取变量为A准备改为C。 此时线程2将变量A - B - A。 线程1的 CAS 操作执行时发现变量仍为 A单状态已丢失中间变化。如何解决Java 提供的工具类会在 CAS 操作中增加版本号Stamp或标记每次修改都更新版本号使得即使值相同也能识别变更历史。比如可以用AtomicStampedReference来解决 ABA 问题通过比对值和版本号识别 ABA 问题。AtomicStampedReferenceInteger ref new AtomicStampedReference(100, 0); // 尝试修改并更新版本号 boolean success ref.compareAndSet(100, 200, 0, 1); // 前提当前值等于100且版本号等于0才会更新为200 1并返回 true循环时间长开销大自旋 CAS 的方式如果长时间不成功会给 CPU 带来很大的开销。只能保证一个共享变量的原子操作只对一个共享变量操作可以保证原子性但是多个则不行多个可以通过AtomicReference或者锁Synchronized实现。为什么不能所有的锁都用 CASCAS 操作是基于循环重试的机制如果 CAS 操作一直未成功线程会一直自旋重试占用 CPU 资源。在高并发场景下大量线程自旋会导致 CPU 资源被浪费。典型应用在Unsafe类中提供了compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong方法来实现对Object、int、long类型的CAS操作。以compareAndSwapInt为例publicfinalnativebooleancompareAndSwapInt(Objecto,longoffset,intexpected,intx);参数中o为需要更新的对象offset为这个对象中整形字段的偏移量如果这个值与expected相同则将字段的值设为x这个新值并且此更新是不可中断的也就是一个原子操作。下面是一个使用compareAndSwapInt的例子privatevolatileinta0;// 共享变量初始值设为 0privatestaticfinalUnsafeunsafe;privatestaticfinallongfieldOffset;static{try{// 获取 Unsafe 实例FieldtheUnsafeUnsafe.class.getDeclaredField(theUnsafe);theUnsafe.setAccessible(true);unsafe(Unsafe)theUnsafe.get(null);// 获取字段 a 的偏移量fieldOffsetunsafe.objectFieldOffset(CasTest.class.getDeclaredField(a));}catch(Exceptione){thrownewRuntimeException(Failed to initialize Unsafe or field offset,e);}}publicstaticvoidmain(String[]args){CasTestcasTestnewCasTest();Threadt1newThread(()-{for(inti1;i4;i){casTest.incrementAndPrint(i);}});Threadt2newThread(()-{for(inti5;i9;i){casTest.incrementAndPrint(i);}});t1.start();t2.start();// 等待线程结束以便观察完整输出try{t1.join();t2.join();}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}// 将递增和打印封装在一个强原子性的方法内privatevoidincrementAndPrint(inttargetValue){while(true){intcurrentValuea;// 读取当前 a 的值if(currentValuetargetValue-1){if(unsafe.compareAndSwapInt(this,fieldOffset,currentValue,targetValue)){// CAS 成功则将 a 的值设置为 targetValueSystem.out.println(targetValue );break;// 成功更新并打印后跳出循环}// 如果 CAS 失败意味着在读取 currentValue 和执行 CAS 之间a 的值被其他线程修改了// 此时 currentValue 已经不是 a 的最新值需要重新读取并重试。}// 如果 currentValue ! targetValue - 1说明还没轮到当前线程更新// 或者已经被其他线程更新超过了让出 CPU 给其他线程机会Thread.yield();// 提示 CPU 调度器可以切换线程减少无效自旋}}在上述例子中我们创建了两个线程他们都尝试修改共享变量 a。每个线程在调用incrementAndPrint(targetValue)方法时会先读取当前 a 的值。判断currentValue是否等于targetValue - 1即期望值的前一个值。如果条件满足则调用unsafe.compareAndSwapInt()尝试将 a 从currentValue更新到targetValue。如果 CAS 操作成功返回 true打印targetValue并退出循环。如果 CAS 失败或者currentValue不满足条件则当前线程会继续循环自旋并通过Thread.yield()尝试让出 CPU直到成功更新并打印或者条件满足。这种机制确保了每个数字从 1 到 9只会被成功设置并打印一次并且是按顺序进行的。需要注意的是compareAndSwapInt本身是只执行一次比较和交换操作并立即返回结果。因此为了确保操作最终成功我们需要在代码中显示的实现自旋逻辑如while(true)循环不断尝试知道 CAS 操作成功。AtomicInteger的实现JDK中的java.util.concurrent.atomic.AtomicInteger类内部正是用了类似的 CAS 操作和自旋逻辑来实现其原子性的getAndIncrement()、compareAndSet()等方法。直接使用AtomicInteger通常是更安全的做法因为它封装了底层的复杂性。CPU 消耗长时间的自旋会消耗 CPU 的资源。在竞争激烈或条件长时间不满足的情况下可以考虑加入更复杂的退避策略Thread.sleep()、LockSupport.parkNanos()来优化。AQSAQS 是什么AQSAbstractQueuedSynchronizer抽象队列同步器是从 JDK1.5 开始提供的 Java 并发核心组件。AQS 解决了开发者在实现同步器时的复杂性问题。它提供了一个通用框架用于实现各种同步器例如可重入锁ReentrantLock、信号量Semaphore和倒计时器CountDownLatch。通过封装底层的线程同步机制AQS 将复杂的线程管理逻辑隐藏起来使开发者只需要专注于具体的同步逻辑。简单来说AQS 是一个抽象类为同步器提供了通用的执行框架。它定义了资源获取和释放的通用流程而具体的资源获取逻辑则由具体同步器通过重写模板方法来实现。因此可以将 AQS 看作是同步器的基础底座而同步器则是基于 AQS 实现的具体应用。AQS 的原理是什么AQS 核心思想是如果被请求的共享资源空闲则将当前请求资源的线程设置为有效的工作线程并且将共享资源设置为锁定状态。如果被请求的共享资源被占用那么就需要一套线程阻塞等待以及被唤醒时所分配的机制这个机制 AQS 是基于CLH 锁进一步优化实现的。CLH 锁对自旋锁进行了改进是基于单链表的自旋锁。在多线程场景下会将请求获取锁的线程组织成一个单项队列每个等待的线程会通过自旋访问前一个线程节点的状态前一个节点释放锁之后当前节点才可以获取锁。CLH 锁的队列结构如下图所示。AQS 中使用的等待队列是 CLH 锁队列的变体。AQS 的 CLH 变体队列是一个双向队列会将暂时获取不到锁的线程加入到该队列中CLH 变体队列和原本的 CLG 锁队列的区别主要有两点由自旋优化为自旋 阻塞自旋操作的性能很高但大量的自选操作比较占用 CPU 资源因此在 CLH 变体队列中会优先通过自旋锁尝试获取锁如果失败再进行阻塞等待。由单项队列优化为双向队列在 CLH 变体队列中会对等待的线程进行阻塞操作当队列前边的线程释放锁之后需要对后边的线程进行唤醒因此增加了 next 指针成为了双向队列。AQS 将每条请求共享资源的线程封装成一个 CLH 变体队列的一个节点Node来实现锁的分配。在 CLH 变体队列中一个节点表示一个线程它保存着线程的引用thread、当前节点在队列中的状态waitStatus、前驱节点prev、后继节点next。AQS 中的 CLH 变体队列结构如下图所示AQSAbstractQueueSynchronized的核心原理图AQS 使用init 成员变量state表示同步状态通过内置的线程等待队列来完成获取资源线程的排队工作。state变量由volatile修饰用于展示当前临界资源的获锁情况。// 共享变量使用volatile修饰保证线程可见性privatevolatileintstate;另外状态信息state可以通过protected类型的getState()、setState()和compareAndSwap()进行操作。并且这几个方法都是final修饰的在子类中无法被重写。//返回同步状态的当前值protectedfinalintgetState(){returnstate;}// 设置同步状态的值protectedfinalvoidsetState(intnewState){statenewState;}//原子地CAS操作将同步状态值设置为给定值update如果当前同步状态的值等于expect期望值protectedfinalbooleancompareAndSetState(intexpect,intupdate){returnunsafe.compareAndSwapInt(this,stateOffset,expect,update);}以ReentrantLock为例state初始值为 0表示未锁定状态。A 线程lock()时会调用tryAcquire()独占该锁并将state 1。此后其他线程再tryAcquire()就会失败知道 A 线程unlock()到state 0即释放锁为止其他线程才有机会获取该锁。当然释放锁之前A 线程自己是可以重复获取锁的此时state会累加这就是可重入的概念。但是获取多少次就要释放多少次这样才能保证state是能回到零态的。再以CountDownLatch以例任务分为 N个子线程去执行state也初始化 N注意 N 要与线程个数一致。这 N 个子线程是并行执行的每个子线程执行完后countDown()一次state会 CAS 减 1。等到所有子线程都执行完后即state 0会 unpark() 主调用线程然后主调用线程就会从wait()函数返回继续后续动作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询